├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── NOTICES ├── README.md ├── TODO.md ├── contrib ├── dotnet │ ├── JonesClient.sln │ ├── JonesClient │ │ ├── JonesClient.csproj │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ └── SimpleJonesClient.cs │ ├── JonesClientTest │ │ ├── App.config │ │ ├── JonesClientTest.csproj │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ └── UnitTest1.cs │ ├── JonesClientTestConsoleApp │ │ ├── App.config │ │ ├── JonesClientTestConsoleApp.csproj │ │ ├── Program.cs │ │ └── Properties │ │ │ └── AssemblyInfo.cs │ ├── Lib │ │ ├── FastSocket.Client.dll │ │ ├── FastSocket.SocketBase.dll │ │ └── Zookeeper.dll │ └── README.txt ├── go-jones │ └── jones.go └── ruby │ ├── .gitignore │ ├── Gemfile │ ├── jones.gemspec │ └── lib │ └── jones.rb ├── cookbook ├── .gitignore ├── Cheffile ├── README.md ├── Vagrantfile ├── attributes │ └── default.rb ├── chefignore ├── metadata.json ├── metadata.rb ├── recipes │ └── default.rb └── templates │ └── default │ └── config.py.erb ├── jones ├── __init__.py ├── client.py ├── jones.py ├── jonesconfig.py ├── static │ ├── css │ │ ├── bootstrap.css │ │ ├── bootstrap.min.css │ │ ├── global.css │ │ └── jsoneditor.css │ ├── favicon.ico │ ├── img │ │ ├── glyphicons-halflings-white.png │ │ ├── glyphicons-halflings.png │ │ └── jsoneditor │ │ │ ├── add_gray.png │ │ │ ├── add_green.png │ │ │ ├── array_blue.png │ │ │ ├── array_gray.png │ │ │ ├── auto_blue.png │ │ │ ├── auto_gray.png │ │ │ ├── delete_gray.png │ │ │ ├── delete_red.png │ │ │ ├── dots_blue.gif │ │ │ ├── dots_blue.xcf │ │ │ ├── dots_gray.gif │ │ │ ├── dots_gray.xcf │ │ │ ├── dots_lightgray.gif │ │ │ ├── dots_lightgray.xcf │ │ │ ├── duplicate_blue.png │ │ │ ├── duplicate_gray.png │ │ │ ├── empty_blue.png │ │ │ ├── empty_gray.png │ │ │ ├── images.html │ │ │ ├── license.txt │ │ │ ├── object_blue.png │ │ │ ├── object_gray.png │ │ │ ├── string_blue.png │ │ │ ├── string_gray.png │ │ │ ├── treeDownTriangleBlack.png │ │ │ ├── treeLeftTriangleBlack.png │ │ │ └── treeRightTriangleBlack.png │ ├── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── jquery-1.7.2.min.js │ │ ├── json2.js │ │ ├── jsoneditor-min.js │ │ ├── service.js │ │ ├── underscore-min.js │ │ └── underscore-min.map │ └── prettify │ │ ├── lang-apollo.js │ │ ├── lang-clj.js │ │ ├── lang-css.js │ │ ├── lang-go.js │ │ ├── lang-hs.js │ │ ├── lang-lisp.js │ │ ├── lang-lua.js │ │ ├── lang-ml.js │ │ ├── lang-n.js │ │ ├── lang-proto.js │ │ ├── lang-scala.js │ │ ├── lang-sql.js │ │ ├── lang-tex.js │ │ ├── lang-vb.js │ │ ├── lang-vhdl.js │ │ ├── lang-wiki.js │ │ ├── lang-xq.js │ │ ├── lang-yaml.js │ │ ├── prettify.css │ │ └── prettify.js ├── templates │ ├── index.j2 │ ├── layout.j2 │ └── service.j2 ├── web.py └── zkutil.py ├── setup.py └── tests ├── __init__.py ├── fixture.py ├── test_client.py ├── test_jones.py ├── test_zkutil.py └── test_znode_map.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | venv 3 | 4 | # Packages 5 | *.egg 6 | *.egg-info 7 | dist 8 | build 9 | eggs 10 | parts 11 | bin 12 | var 13 | sdist 14 | develop-eggs 15 | .installed.cfg 16 | 17 | # Installer logs 18 | pip-log.txt 19 | 20 | # Unit test / coverage reports 21 | .coverage 22 | .tox 23 | 24 | #Translations 25 | *.mo 26 | 27 | #Mr Developer 28 | .mr.developer.cfg 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | 6 | notifications: 7 | email: 8 | - mwhooker@gmail.com 9 | 10 | before_install: 11 | - sudo apt-get update >/dev/null 2>&1 12 | - sudo apt-get install zookeeper 2>&1 13 | - sudo apt-get install libevent-dev 14 | 15 | install: 16 | - pip install "git+https://github.com/python-zk/kazoo.git" 17 | - python setup.py develop 18 | - pip install --use-mirrors jones[test] 19 | 20 | script: ZOOKEEPER_PATH=/usr/share/java nosetests -d 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2012 DISQUS 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | prune jones 3 | include jones/__init__.py 4 | include jones/client.py 5 | -------------------------------------------------------------------------------- /NOTICES: -------------------------------------------------------------------------------- 1 | Jones Configuration Management 2 | Copyright 2012 DISQUS 3 | Copyright 2013 Matthew Hooker 4 | 5 | Portions of this software were developed at Disqus (http://disqus.com) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jones 2 | 3 | [![travis][2]][1] 4 | 5 | Jones is a configuration frontend for Zookeeper. 6 | 7 | ## Goals 8 | 9 | * Clients MUST only talk to zookeeper 10 | * Accessing configuration MUST be simple (i.e. no computation) 11 | * Unique views of the config must be available on a host-by-host basis 12 | 13 | ## Introduction 14 | 15 | At their root, most configuration systems are a hierarchy of dictionaries. The 16 | root has config common to all environments, with config specific to say, 17 | developers or a staging area, inheriting and overriding values. Jones takes 18 | this idea and maps it to Zookeeper. 19 | 20 | Zookeeper is the ideal place for configuration. Besides it's availability 21 | guarantees, it's also able to update observers when data changes. Now we can 22 | change config at runtime, making possible a whole category of use-cases like 23 | switches, a/b tests, and knob and lever you can imagine. 24 | 25 | For more information, see my 26 | [talk](http://pyvideo.org/video/1567/configuration-management-with-zookeeper) 27 | and [presentation](https://speakerdeck.com/mwhooker/jones) at Pycon Canada. 28 | 29 | ## Running the Server 30 | 31 | Jones uses the [Flask](http://flask.pocoo.org/) web framework. For development, 32 | running the server is as simple as `python jones/web.py`. 33 | 34 | For running in production, I recommend using [Gunicorn](http://gunicorn.org/) 35 | with an http frontend like nginx or apache. Gunicorn can run Jones with 36 | `gunicorn jones.web:app`. For more information on running gunicorn see [Running 37 | Gunicorn](http://docs.gunicorn.org/en/latest/run.html). For help on deploying 38 | gunicorn with nginx, see [Deploying 39 | Gunicorn](http://docs.gunicorn.org/en/latest/deploy.html) 40 | 41 | ### Configuring 42 | 43 | Jones uses [Flask's configuration 44 | handling](http://flask.pocoo.org/docs/config/). It comes wsith a [default 45 | config](https://github.com/mwhooker/jones/blob/master/jones/jonesconfig.py) 46 | which should modified before running the server. 47 | 48 | You can override it by creating your own config with the right values plugged 49 | in, and setting the environmental variable JONES_SETTINGS to the path to that 50 | file. 51 | 52 | ## Using the client 53 | 54 | Jones comes with an example client, which we hope will serve the most general 55 | case. It's also incredibly simple (only 30 lines), so it should be easy to 56 | customize. Using it is just as straight-forward. 57 | 58 | **Install:** 59 | 60 | pip install jones 61 | 62 | **Use:** 63 | 64 | from jones.client import JonesClient 65 | 66 | # Initialize jones client with kazoo connection, and service. 67 | jones = JonesClient(zk, 'da') 68 | client['key'] 69 | 'value' 70 | client.get('missingkey', 'default') 71 | 'default' 72 | 73 |
74 |
zk
75 |
An instance of kazoo.client.KazooClient.
76 |
service
77 |
The name of the service you want config for.
78 |
79 | 80 | The JonesClient object also takes an optional callback and association. 81 | 82 |
83 |
cb
84 |
A method to be called with a config dict every time it changes.
85 |
association
86 |
A key in the _associations_ map. By default JonesClient uses socket.getfqdn().
87 |
88 | 89 | ## Design 90 | 91 | Environments are stored under their parent znodes on the zookeeper data tree. 92 | On write, the view algorithm is used to materialize the "inherited" config in 93 | a view node. 94 | 95 | Jones takes advantage of zookeeper's mvcc capabilities where possible. An 96 | environment will never have its data clobbered by a concurrent write. When 97 | updating a view, however, the last write wins. This may cause view data to be 98 | clobbered if concurrent writes are made to two nodes in the same path and Jones 99 | happens to lose its session in between (see issue #1). 100 | 101 | Associations are a simple key to env map, stored under /nodemaps. 102 | 103 | Example data tree dump. This shows data for an example service: 104 | 105 | ``` 106 | / 107 | /services 108 | /services/test 109 | /services/test/nodemaps 110 | {"example": "/services/test/views/child1/sib"} 111 | /services/test/conf 112 | {"foo": "bar", "fiesasld": "value31"} 113 | /services/test/conf/child1 114 | {"field": "HAILSATAN"} 115 | /services/test/conf/child1/sib 116 | {"foo": "big"} 117 | /services/test/conf/child1/baby 118 | {"foo": "baz"} 119 | /services/test/views 120 | {"foo": "bar", "fiesasld": "value31"} 121 | /services/test/views/child1 122 | {"field": "HAILSATAN", "foo": "bar", "fiesasld": "value31"} 123 | /services/test/views/child1/sib 124 | {"field": "HAILSATAN", "foo": "big", "fiesasld": "value31"} 125 | /services/test/views/child1/baby 126 | {"field": "HAILSATAN", "foo": "baz", "fiesasld": "value31"} 127 | /services/test2 128 | /services/test2/nodemaps 129 | /services/test2/conf 130 | {} 131 | /services/test2/views 132 | {} 133 | ``` 134 | 135 | ## Glossary 136 | 137 |
138 |
Config Tree
139 |
The hierarchy of nodes.
140 |
Node
141 |
A node in the config tree. Nodes hold configuration for an environment. Implemented as a znode.
142 |
Environment
143 |
Also seen as env in the code, an environment is the path to a specific node in the config tree 144 | (i.e. parent/child).
145 |
Association
146 |
The identifier a client will use to address a node. Any string will work, but the fqdn or ip address are common.
147 |
View
148 |
A view is a node which has has the following algorithm applied 149 |
for node in root -> env
150 |   update view with node.config
151 |
152 |
153 | 154 | ## Changelog 155 | 156 | Jones uses [Semantic Versioning](http://semver.org/). 157 | 158 | > Given a version number MAJOR.MINOR.PATCH, increment the: 159 | 160 | > MAJOR version when you make incompatible API changes, 161 | MINOR version when you add functionality in a backwards-compatible manner, and 162 | PATCH version when you make backwards-compatible bug fixes. Additional labels 163 | for pre-release and build metadata are available as extensions to the 164 | MAJOR.MINOR.PATCH format. 165 | 166 | ### About Versioning 167 | 168 | Only the python client is packaged and available on pypi. Thus, the version 169 | number only applies to changes to the client. We intend that the web server be 170 | checked out of version control, and leave it up to the user to decide which 171 | version to use. We'll try to make sure the server and clients maintain back and 172 | forwards compatibility. This may be a mistake, but since the client and server 173 | are essentially frozen at this point, we'll leave it as it is for now. If 174 | we make any changes impacing bc/fc, we'll have to revisit this. 175 | 176 | 177 | ### 0.7.0 178 | 179 | * Upgraded to Bootstrap 3.0rc1 180 | * Turned the loosely defined `env` into a type 181 | * Fixed numerous bugs and style issues 182 | 183 | ### 1.0.0 184 | 185 | * Updated Kazoo to 1.12.1 186 | * Rewrote the ZKNodeMap class to serialize to json instead of the legacy format. 187 | * the code is smart enough to update the map format on the fly, but I advise you to test on your set up, first. 188 | 189 | ## Screenshot 190 | ![Example](http://mwhooker.github.com/jones/docs/img/testservice.png) 191 | 192 | [1]: https://travis-ci.org/mwhooker/jones 193 | [2]: https://travis-ci.org/mwhooker/jones.png?branch=master 194 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | * Add versions to services controller. (DONE) 3 | * Implement update. (DONE) 4 | * fuck modals. just drop a textinput below the li/tr (DONE) 5 | * Mechanism for creating new services. (DONE) 6 | * UI. Adding a child should redirect you to that node (DONE) 7 | * Deleting root node should bounce you to index (DONE) 8 | * implement UI code for associations. (DONE) 9 | * UI. Deleting root node should prompt for confirmation 10 | * UI. Catch exceptions and flash messages (or do '/' validation) 11 | * validate node name client-side before accepting creation. 12 | * Support versions, rollback, and pegging to a version 13 | * Log every action so we can recreate in case of accident. 14 | * show raw json view. 15 | * add tooltips to inherited view and associations. 16 | * warn user if their update failed because of version conflict 17 | * preserve user's changes 18 | * use backbone.js for associations. 19 | * monitor zk connection state 20 | * if no connection can be made, send app into reduced functionality mode 21 | * integration tests 22 | 23 | # Kazoo 24 | implement: 25 | walk (DONE) 26 | resolve (DONE) 27 | ln (DONE) 28 | test: 29 | ZNodeLink (DONEish) 30 | fix: 31 | travis-ci (DONE) 32 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClient.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.21005.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JonesClient", "JonesClient\JonesClient.csproj", "{E19B20BC-15E9-48D6-933E-9737C5314A5D}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JonesClientTest", "JonesClientTest\JonesClientTest.csproj", "{6681B168-EEC1-47E1-927F-CAA16FCB91F0}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JonesClientTestConsoleApp", "JonesClientTestConsoleApp\JonesClientTestConsoleApp.csproj", "{A17DA404-E9D5-4A1F-A0D2-089A6B9B44D6}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {E19B20BC-15E9-48D6-933E-9737C5314A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {E19B20BC-15E9-48D6-933E-9737C5314A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {E19B20BC-15E9-48D6-933E-9737C5314A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {E19B20BC-15E9-48D6-933E-9737C5314A5D}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {6681B168-EEC1-47E1-927F-CAA16FCB91F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {6681B168-EEC1-47E1-927F-CAA16FCB91F0}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {6681B168-EEC1-47E1-927F-CAA16FCB91F0}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {6681B168-EEC1-47E1-927F-CAA16FCB91F0}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {A17DA404-E9D5-4A1F-A0D2-089A6B9B44D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {A17DA404-E9D5-4A1F-A0D2-089A6B9B44D6}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {A17DA404-E9D5-4A1F-A0D2-089A6B9B44D6}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {A17DA404-E9D5-4A1F-A0D2-089A6B9B44D6}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClient/JonesClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E19B20BC-15E9-48D6-933E-9737C5314A5D} 8 | Library 9 | Properties 10 | JonesClient 11 | JonesClient 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | False 43 | ..\Lib\Zookeeper.dll 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 58 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClient/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JonesClient")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JonesClient")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("72c86c0e-7320-4a24-bdf8-7479547034ba")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClient/SimpleJonesClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Sodao.Zookeeper; 7 | using Sodao.Zookeeper.Data; 8 | using System.Web.Script.Serialization; 9 | 10 | namespace JonesClient 11 | { 12 | /// 13 | /// Our simple Jones Client. Configuration strings are stored in a Dictionary 14 | /// 15 | public class SimpleJonesClient 16 | { 17 | /// 18 | /// The hostname 19 | /// 20 | public string HostName { set; get; } 21 | 22 | /// 23 | /// Service name 24 | /// 25 | public string ServiceName { set; get; } 26 | 27 | /// 28 | /// The Dictionary that holds the configuration 29 | /// 30 | public Dictionary Config { get; set; } 31 | 32 | /// 33 | /// Our ZooKeeper client connection object 34 | /// 35 | protected IZookClient _zkclient = Sodao.Zookeeper.ZookClientPool.Get("zk1"); 36 | protected string NodeMapPath; 37 | protected IWatcher ConfigViewNodeChangeWatcher; 38 | 39 | /// 40 | /// The Jones client constructor 41 | /// 42 | /// Service Name 43 | /// Hostname 44 | /// Zookeeper Client connection object 45 | /// Callback function for any changes to the config tree 46 | public SimpleJonesClient(string service, string hostname = null, IZookClient zkclient = null, Action> callback = null) 47 | { 48 | // initialize the hostname 49 | if (String.IsNullOrEmpty(hostname)) 50 | { 51 | // get FDQN 52 | // http://stackoverflow.com/questions/804700/how-to-find-fqdn-of-local-machine-in-c-net 53 | // http://support.microsoft.com/kb/303902 54 | HostName = System.Net.Dns.GetHostEntry("LocalHost").HostName.ToLower(); 55 | 56 | int idx = HostName.IndexOf("."); 57 | if (idx > 0) 58 | { 59 | HostName = HostName.Substring(0, idx); 60 | } 61 | } 62 | 63 | if (zkclient != null) 64 | { 65 | _zkclient = zkclient; 66 | } 67 | 68 | // initialize the dictionary to hold the configurations 69 | Config = new Dictionary(); 70 | 71 | // set the service name 72 | ServiceName = service; 73 | 74 | // this is our callback for Config View ZNode changes, to refresh our 75 | // this.Config 76 | ConfigViewNodeChangeWatcher = new WatcherWrapper(e => 77 | { 78 | _zkclient.GetData(e.Path, ConfigViewNodeChangeWatcher).ContinueWith(d => 79 | { 80 | string conf_data = Encoding.UTF8.GetString(d.Result.Data); 81 | 82 | Config = DeserializeNodeMap(conf_data); 83 | 84 | if (callback != null) 85 | { 86 | callback.Invoke(Config); 87 | } 88 | }); 89 | }); 90 | 91 | NodeMapPath = String.Format(@"/services/{0}/nodemaps", ServiceName); 92 | 93 | #region get the current Config from the view 94 | 95 | string current_conf_path = ""; 96 | var tnm = _zkclient.GetData(NodeMapPath, false).ContinueWith(c => 97 | { 98 | string conf_path_json = Encoding.UTF8.GetString(c.Result.Data); 99 | 100 | try 101 | { 102 | current_conf_path = DeserializeNodeMap(conf_path_json)[HostName]; 103 | } 104 | catch (Exception) 105 | { 106 | // fail-safe default configs when there's no machine-specific config is found 107 | current_conf_path = String.Format(@"/services/{0}/conf", ServiceName); 108 | } 109 | 110 | var td = _zkclient.GetData(current_conf_path, ConfigViewNodeChangeWatcher).ContinueWith(d => 111 | { 112 | // this part is important so this.Config will not be empty on constructor completion! 113 | string conf_data = Encoding.UTF8.GetString(d.Result.Data); 114 | 115 | Config = DeserializeNodeMap(conf_data); 116 | }); 117 | 118 | td.Wait(); // synchronous wait 119 | }); 120 | 121 | tnm.Wait(); // synchronous wait 122 | 123 | #endregion 124 | 125 | } 126 | 127 | /// 128 | /// Helper function to deserialize string to a JSON object 129 | /// 130 | /// JSON string (presumably from a node map) 131 | /// Dictionary of keys and values 132 | public static Dictionary DeserializeNodeMap(string json_nodemap_data) 133 | { 134 | JavaScriptSerializer jss = new JavaScriptSerializer(); 135 | var d = jss.Deserialize>(json_nodemap_data); 136 | 137 | return d; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClientTest/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClientTest/JonesClientTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {6681B168-EEC1-47E1-927F-CAA16FCB91F0} 7 | Library 8 | Properties 9 | JonesClientTest 10 | JonesClientTest 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | False 41 | ..\Lib\Zookeeper.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {e19b20bc-15e9-48d6-933e-9737c5314a5d} 66 | JonesClient 67 | 68 | 69 | 70 | 71 | 72 | 73 | False 74 | 75 | 76 | False 77 | 78 | 79 | False 80 | 81 | 82 | False 83 | 84 | 85 | 86 | 87 | 88 | 89 | 96 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClientTest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JonesClientTest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JonesClientTest")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c1447b02-3ecd-40a1-8913-1a0459904e97")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClientTest/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using JonesClient; 4 | using Sodao.Zookeeper; 5 | 6 | namespace JonesClientTest 7 | { 8 | [TestClass] 9 | public class UnitTest1 10 | { 11 | [TestMethod] 12 | public void TestCreateSimpleJonesClient() 13 | { 14 | var jc = new SimpleJonesClient(service: "yoda"); 15 | 16 | //while (true) 17 | { 18 | System.Threading.Thread.Sleep(1000); 19 | 20 | foreach (var key in jc.Config.Keys) 21 | { 22 | Console.WriteLine("{0} = {1}", key, jc.Config[key]); 23 | } 24 | } 25 | System.Console.ReadLine(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClientTestConsoleApp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClientTestConsoleApp/JonesClientTestConsoleApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A17DA404-E9D5-4A1F-A0D2-089A6B9B44D6} 8 | Exe 9 | Properties 10 | JonesClientTestConsoleApp 11 | JonesClientTestConsoleApp 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | False 44 | ..\Lib\Zookeeper.dll 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {e19b20bc-15e9-48d6-933e-9737c5314a5d} 57 | JonesClient 58 | 59 | 60 | 61 | 68 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClientTestConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using JonesClient; 8 | 9 | namespace JonesClientTestConsoleApp 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | var jc = new SimpleJonesClient(service: "yoda", callback: (d => { 16 | Console.ForegroundColor = ConsoleColor.Yellow; 17 | Console.WriteLine("config has changed: we now have {0} config items", d.Keys.Count); 18 | Console.ForegroundColor = ConsoleColor.Gray; 19 | 20 | foreach (var k in d.Keys) 21 | { 22 | Console.WriteLine("{0} = {1}", k, d[k]); 23 | } 24 | 25 | Console.WriteLine("-"); 26 | 27 | })); 28 | 29 | Console.WriteLine("hello, world!"); 30 | Console.WriteLine("service {0} has {1} config items", jc.ServiceName, jc.Config.Keys.Count); 31 | foreach (var key in jc.Config.Keys) 32 | { 33 | Console.WriteLine("{0} = {1}", key, jc.Config[key]); 34 | } 35 | 36 | Console.WriteLine("-"); 37 | Console.ReadKey(false); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contrib/dotnet/JonesClientTestConsoleApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("JonesClientTestConsoleApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("JonesClientTestConsoleApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("561e9446-55a5-4cc2-9b4c-722137404cd4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /contrib/dotnet/Lib/FastSocket.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/contrib/dotnet/Lib/FastSocket.Client.dll -------------------------------------------------------------------------------- /contrib/dotnet/Lib/FastSocket.SocketBase.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/contrib/dotnet/Lib/FastSocket.SocketBase.dll -------------------------------------------------------------------------------- /contrib/dotnet/Lib/Zookeeper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/contrib/dotnet/Lib/Zookeeper.dll -------------------------------------------------------------------------------- /contrib/dotnet/README.txt: -------------------------------------------------------------------------------- 1 | This is a Jones Client for .NET 2 | 3 | Requires: 4 | 5 | .NET 4.0 6 | ZooKeeper.NET from Jon Hong: https://github.com/devhong/Zookeeper.Net -------------------------------------------------------------------------------- /contrib/go-jones/jones.go: -------------------------------------------------------------------------------- 1 | package jones 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/samuel/go-zookeeper/zk" 7 | "os" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | type watcher struct { 13 | conn *zk.Conn 14 | path string 15 | cb func([]byte) 16 | stop chan struct{} 17 | } 18 | 19 | func (w *watcher) watch() { 20 | for { 21 | data, _, events, err := w.conn.GetW(w.path) 22 | if err != nil { 23 | continue 24 | } 25 | w.cb(data) 26 | select { 27 | case <-events: 28 | case <-w.stop: 29 | return 30 | } 31 | } 32 | } 33 | 34 | func (w *watcher) close() { 35 | w.stop <- struct{}{} 36 | } 37 | 38 | type JonesClient struct { 39 | conn *zk.Conn 40 | hostname string 41 | nodemapWatcher *watcher 42 | configWatcher *watcher 43 | service string 44 | nodemap map[string]string 45 | config map[string]interface{} 46 | synced bool 47 | wg sync.WaitGroup 48 | } 49 | 50 | func (client *JonesClient) DialAndSync(servers []string) error { 51 | err := client.Dial(servers) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | client.wg.Wait() 57 | return nil 58 | } 59 | 60 | func (client *JonesClient) Dial(servers []string) error { 61 | var err error 62 | client.conn, _, err = zk.Connect(servers, time.Second) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | client.wg.Add(1) 68 | 69 | client.nodemapWatcher = &watcher{ 70 | conn: client.conn, 71 | path: fmt.Sprintf("/services/%s/nodemaps", client.service), 72 | cb: client.nodemapChanged, 73 | } 74 | go client.nodemapWatcher.watch() 75 | return nil 76 | } 77 | 78 | func (client *JonesClient) nodemapChanged(data []byte) { 79 | if len(data) > 0 { 80 | json.Unmarshal(data, &client.nodemap) 81 | } 82 | conf, ok := client.nodemap[client.hostname] 83 | if !ok { 84 | conf = fmt.Sprintf("/services/%s/conf", client.service) 85 | } 86 | 87 | if client.configWatcher != nil { 88 | client.configWatcher.close() 89 | } 90 | 91 | client.configWatcher = &watcher{ 92 | conn: client.conn, 93 | path: conf, 94 | cb: client.configChanged, 95 | } 96 | go client.configWatcher.watch() 97 | } 98 | 99 | func (client *JonesClient) configChanged(data []byte) { 100 | if len(data) > 0 { 101 | json.Unmarshal(data, &client.config) 102 | } 103 | if !client.synced { 104 | client.synced = true 105 | client.wg.Done() 106 | } 107 | } 108 | 109 | func (client *JonesClient) Get(key string) interface{} { 110 | return client.config[key] 111 | } 112 | 113 | func New(service string) *JonesClient { 114 | client := &JonesClient{service: service} 115 | client.hostname, _ = os.Hostname() 116 | return client 117 | } 118 | -------------------------------------------------------------------------------- /contrib/ruby/.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | -------------------------------------------------------------------------------- /contrib/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "zk" 4 | -------------------------------------------------------------------------------- /contrib/ruby/jones.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'jones' 3 | s.version = '0.2.0' 4 | s.date = '2013-08-05' 5 | s.summary = "Client for Jones configuration management server" 6 | s.authors = ["Matthew Hooker"] 7 | s.email = 'mwhooker@gmail.com' 8 | s.files = ["lib/jones.rb"] 9 | s.homepage = 'https://github.com/mwhooker/jones' 10 | s.license = 'Apache 2.0' 11 | s.add_runtime_dependency "zk", ["= 1.8.0"] 12 | end 13 | -------------------------------------------------------------------------------- /contrib/ruby/lib/jones.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | =end 14 | 15 | 16 | require 'forwardable' 17 | require 'json' 18 | require 'socket' 19 | require 'zk' 20 | 21 | 22 | class JonesClient 23 | extend Forwardable 24 | attr_accessor :data 25 | def_delegators :@data, :[] 26 | 27 | def initialize(options = {}) 28 | @data = nil 29 | @callbacks = [] 30 | @conf_sub = nil 31 | @conf_path = nil 32 | @logger = Logger.new(STDOUT) 33 | 34 | parse_options(options) 35 | setup_zk 36 | read_nodemap 37 | end 38 | 39 | def register_callback(&block) 40 | @callbacks += [block] 41 | end 42 | 43 | private 44 | 45 | def nodemap_changed(event) 46 | if event.node_changed? 47 | read_nodemap 48 | else 49 | @logger.error("Unknown ZK node event: #{event.inspect}") 50 | end 51 | end 52 | 53 | def read_nodemap 54 | data = @zk.get(nodemap_path, :watch => true).first 55 | if data.nil? 56 | conf_path = conf_root 57 | else 58 | mapping = JSON.load(data) 59 | conf_path = mapping.fetch(@host, conf_root) 60 | end 61 | 62 | setup_conf(conf_path) 63 | end 64 | 65 | def setup_conf(conf_path) 66 | if conf_path != @conf_path 67 | # path changed so we need to register a new handler 68 | @conf_path = conf_path 69 | @conf_sub.unsubscribe unless @conf_sub.nil? 70 | @conf_sub = @zk.register(@conf_path) { |event| conf_changed(event) } 71 | read_conf 72 | end 73 | end 74 | 75 | def conf_changed(event) 76 | if event.node_changed? 77 | read_conf 78 | else 79 | logger.error("Unknown ZK node event: #{event.inspect}") 80 | end 81 | end 82 | 83 | def read_conf() 84 | @data = JSON.load(@zk.get(@conf_path, :watch => true).first) 85 | @callbacks.each { |cb| cb.call(@data) } 86 | end 87 | 88 | def setup_zk 89 | @zk = ZK.new(@zkservers) if @zkservers 90 | @zk.on_expired_session { setup_zk } 91 | @zk.register(nodemap_path, :only => :changed) { |event| nodemap_changed(event) } 92 | # reset watch in case of disconnect 93 | @zk.on_connected { read_nodemap } 94 | end 95 | 96 | def parse_options(options) 97 | @zk, @zkservers = options.values_at(:zk, :zkservers) 98 | if [@zk, @zkservers].all? || [@zk, @zkservers].none? 99 | raise ArgumentError, 'must specify :zk or :zkservers' 100 | end 101 | 102 | @service = options[:service] 103 | @host = options.fetch(:host, Socket.gethostname) 104 | @zkroot = "/services/#{options[:service]}" 105 | end 106 | 107 | def nodemap_path 108 | "#{@zkroot}/nodemaps" 109 | end 110 | 111 | def conf_root 112 | "#{@zkroot}/conf" 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /cookbook/.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | Berksfile.lock 3 | Gemfile.lock 4 | *~ 5 | *# 6 | .#* 7 | \#*# 8 | .*.sw[a-z] 9 | *.un~ 10 | /cookbooks 11 | -------------------------------------------------------------------------------- /cookbook/Cheffile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #^syntax detection 3 | 4 | site 'http://community.opscode.com/api/v1' 5 | 6 | cookbook 'application' 7 | cookbook 'application_python' 8 | cookbook 'nginx_conf', :git => 'https://github.com/mwhooker/chef-nginx_conf.git' 9 | cookbook 'python' 10 | cookbook 'zookeeper', :git => 'https://github.com/SimpleFinance/chef-zookeeper.git' 11 | 12 | cookbook 'jones', :path => './' 13 | -------------------------------------------------------------------------------- /cookbook/README.md: -------------------------------------------------------------------------------- 1 | Description 2 | =========== 3 | Installs and configures Jones with nginx and gunicorn 4 | 5 | Requirements 6 | ============ 7 | python 8 | nginx\_conf 9 | zookeeper 10 | application 11 | application\_python 12 | 13 | Attributes 14 | ========== 15 | 16 | `node[:jones][:config][:zk_digest_password]` is one you may want to change. By default jones is write-protected and this is how it accomplishes that. 17 | -------------------------------------------------------------------------------- /cookbook/Vagrantfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | box = ENV['VAGRANT_BOX'] || 'opscode_ubuntu-12.04_provisionerless' 4 | Vagrant.configure('2') do |config| 5 | config.vm.hostname = "jones" 6 | config.vm.box = box 7 | config.vm.box_url = "https://opscode-vm.s3.amazonaws.com/vagrant/#{box}.box" 8 | config.omnibus.chef_version = :latest 9 | config.vm.network :forwarded_port, guest: 80, host: 8080 10 | config.vm.network :forwarded_port, guest: 8080, host: 8081 11 | 12 | config.vm.provision :shell do |shell| 13 | shell.inline = 'test -f $1 || (sudo apt-get update -y && touch $1)' 14 | shell.args = '/var/run/apt-get-update' 15 | end 16 | 17 | config.vm.provision :chef_solo do |chef| 18 | chef.json = { 19 | :java => { 20 | :install_flavor => "oracle", 21 | :oracle => { 22 | :accept_oracle_download_terms => true 23 | } 24 | }, 25 | :exhibitor => { 26 | :opts => { 27 | :configtype => "file", 28 | :s3backup => "false" 29 | }, 30 | :snapshot_dir => "/tmp/zookeeper_snapshots", 31 | :transaction_dir => "/tmp/zookeeper_transactions", 32 | :log_index_dir => "/tmp/zookeeper_log_indexes", 33 | :hostname => "http://localhost:8080" 34 | } 35 | } 36 | 37 | chef.run_list = [ 38 | "recipe[zookeeper]", 39 | "recipe[jones]" 40 | ] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /cookbook/attributes/default.rb: -------------------------------------------------------------------------------- 1 | default[:jones][:user] = "jones" 2 | default[:jones][:uid] = "61002" 3 | default[:jones][:group] = "nogroup" 4 | default[:jones][:zk_chroot] = "jones" 5 | # 6 | # pick one 7 | #default[:jones][:zk_connect] = "localhost:2181" 8 | #default[:exhibitor][:hostname] = "localhost:8080" 9 | 10 | default[:jones][:repo] = "git://github.com/mwhooker/jones.git" 11 | default[:jones][:tag] = "releases/1.0.0" 12 | default[:jones][:destination] = "/opt/www/jones" 13 | 14 | default[:jones][:gunicorn][:port] = "8000" 15 | 16 | default[:jones][:config][:debug] = "False" 17 | default[:jones][:config][:testing] = "False" 18 | default[:jones][:config][:secret_key] = 'dev key' 19 | default[:jones][:config][:zk_digest_password] = "changeme" 20 | 21 | default[:python][:distribute_install_py_version] = "2.7" 22 | default[:python][:install_method] = "package" 23 | -------------------------------------------------------------------------------- /cookbook/chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # or sharing to the community site. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | Icon? 9 | nohup.out 10 | ehthumbs.db 11 | Thumbs.db 12 | 13 | # SASS # 14 | ######## 15 | .sass-cache 16 | 17 | # EDITORS # 18 | ########### 19 | \#* 20 | .#* 21 | *~ 22 | *.sw[a-z] 23 | *.bak 24 | REVISION 25 | TAGS* 26 | tmtags 27 | *_flymake.* 28 | *_flymake 29 | *.tmproj 30 | .project 31 | .settings 32 | mkmf.log 33 | 34 | ## COMPILED ## 35 | ############## 36 | a.out 37 | *.o 38 | *.pyc 39 | *.so 40 | *.com 41 | *.class 42 | *.dll 43 | *.exe 44 | */rdoc/ 45 | 46 | # Testing # 47 | ########### 48 | .watchr 49 | .rspec 50 | spec/* 51 | spec/fixtures/* 52 | test/* 53 | features/* 54 | Guardfile 55 | Procfile 56 | 57 | # SCM # 58 | ####### 59 | .git 60 | */.git 61 | .gitignore 62 | .gitmodules 63 | .gitconfig 64 | .gitattributes 65 | .svn 66 | */.bzr/* 67 | */.hg/* 68 | */.svn/* 69 | 70 | # Berkshelf # 71 | ############# 72 | Berksfile 73 | Berksfile.lock 74 | cookbooks/* 75 | tmp 76 | 77 | # Cookbooks # 78 | ############# 79 | CONTRIBUTING 80 | CHANGELOG* 81 | 82 | # Strainer # 83 | ############ 84 | Colanderfile 85 | Strainerfile 86 | .colander 87 | .strainer 88 | 89 | # Vagrant # 90 | ########### 91 | .vagrant 92 | Vagrantfile 93 | 94 | # Travis # 95 | ########## 96 | .travis.yml 97 | -------------------------------------------------------------------------------- /cookbook/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jones", 3 | "description": "Installs/Configures jones", 4 | "long_description": "Description\n===========\nWIP\n\nRequirements\n============\n\nAttributes\n==========\n\nUsage\n=====\n\n", 5 | "maintainer": "Matthew Hooker", 6 | "maintainer_email": "mwhooker@gmail.com", 7 | "license": "All rights reserved", 8 | "platforms": { 9 | }, 10 | "dependencies": { 11 | "python": ">= 0.0.0", 12 | "nginx_conf": ">= 0.0.0", 13 | "zookeeper": ">= 0.0.0", 14 | "application": ">= 0.0.0", 15 | "application_python": ">= 0.0.0" 16 | }, 17 | "recommendations": { 18 | }, 19 | "suggestions": { 20 | }, 21 | "conflicting": { 22 | }, 23 | "providing": { 24 | }, 25 | "replacing": { 26 | }, 27 | "attributes": { 28 | }, 29 | "groupings": { 30 | }, 31 | "recipes": { 32 | }, 33 | "version": "1.0.0" 34 | } -------------------------------------------------------------------------------- /cookbook/metadata.rb: -------------------------------------------------------------------------------- 1 | maintainer "Matthew Hooker" 2 | maintainer_email "mwhooker@gmail.com" 3 | license "All rights reserved" 4 | description "Installs/Configures jones" 5 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 6 | name "jones" 7 | version "1.0.0" 8 | # TODO: depend on build-essential if we're on a debian-like 9 | depends "python" 10 | depends "nginx_conf" 11 | depends "zookeeper" 12 | depends "gunicorn" 13 | depends "nginx" 14 | depends "supervisor" 15 | depends "application" 16 | depends "application_python" 17 | -------------------------------------------------------------------------------- /cookbook/recipes/default.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Cookbook Name:: jones 3 | # Recipe:: default 4 | # 5 | # Copyright 2013, Matthew Hooker 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | include_recipe "gunicorn" 21 | include_recipe "nginx" 22 | include_recipe "supervisor" 23 | package "libevent-dev" 24 | 25 | node.override['nginx']['default_site_enabled'] = false 26 | node.override['nginx_conf']['pre_socket'] = 'http://' 27 | 28 | user node[:jones][:user] do 29 | uid node[:jones][:uid] 30 | gid node[:jones][:group] 31 | end 32 | 33 | directory node[:jones][:destination] do 34 | owner node[:jones][:user] 35 | recursive true 36 | mode "0755" 37 | end 38 | 39 | config_path = "#{node[:jones][:destination]}/shared/jonesconfig.py" 40 | venv = "#{node[:jones][:destination]}/shared/env" 41 | 42 | python_virtualenv venv do 43 | owner "root" 44 | action :create 45 | end 46 | 47 | application "jones" do 48 | path node[:jones][:destination] 49 | packages ["git-core"] 50 | 51 | repository node[:jones][:repo] 52 | revision node[:jones][:tag] 53 | owner node[:jones][:user] 54 | group node[:jones][:group] 55 | 56 | # TODO: environment not being set. 57 | gunicorn do 58 | environment :JONES_SETTINGS => config_path 59 | packages ["gevent", "jones[web]"] 60 | port 8000 61 | workers node[:cpu][:total] + 1 62 | backlog 2048 63 | worker_class "egg:gunicorn#gevent" 64 | app_module "jones.web:app" 65 | virtualenv venv 66 | end 67 | end 68 | 69 | template config_path do 70 | source "config.py.erb" 71 | owner "root" 72 | group "root" 73 | mode "0644" 74 | variables( 75 | :config => node[:jones][:config], 76 | :zk_connect_str => zk_connect_str( 77 | discover_zookeepers(node[:exhibitor][:hostname]), 78 | node[:jones][:zk_chroot]) 79 | ) 80 | end 81 | 82 | supervisor_service "jones" do 83 | subscribes :before_deploy, "application[jones]" 84 | action :start 85 | end 86 | 87 | nginx_conf_file "jones" do 88 | socket "127.0.0.1:8000" 89 | server_name "_" 90 | end 91 | -------------------------------------------------------------------------------- /cookbook/templates/default/config.py.erb: -------------------------------------------------------------------------------- 1 | DEBUG = <%= @config[:debug] %> 2 | TESTING = <%= @config[:testing] %> 3 | ZK_CONNECTION_STRING = "<%= @zk_connect_str %>" 4 | ZK_DIGEST_PASSWORD = "<%= @config[:zk_digest_password] %>" 5 | -------------------------------------------------------------------------------- /jones/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | from __future__ import absolute_import 15 | -------------------------------------------------------------------------------- /jones/client.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | 15 | from collections import Mapping 16 | from kazoo.recipe.watchers import DataWatch 17 | 18 | import socket 19 | import json 20 | 21 | 22 | class EnvironmentNotFoundException(Exception): 23 | pass 24 | 25 | 26 | class JonesClient(Mapping): 27 | """An example client for accessing config stored by Jones. 28 | 29 | :param zk: zookeeper connection. 30 | :type zk: :class:`kazoo.client.KazooClient` 31 | :param service: name of the service to get config for. 32 | :type service: string. 33 | :param cb: optional method to be called with config when it changes. 34 | :type param: function. 35 | :param hostname: Node to get associated configuration data for. 36 | :type hostname: string. 37 | 38 | """ 39 | 40 | def __init__(self, zk, service, cb=None, hostname=None): 41 | self.service = service 42 | self.zk = zk 43 | self.cb = cb 44 | 45 | if not hostname: 46 | hostname = socket.getfqdn() 47 | self.hostname = hostname 48 | 49 | self.nodemap_path = "/services/%s/nodemaps" % service 50 | 51 | self.nodemap_watcher = DataWatch( 52 | self.zk, self.nodemap_path, 53 | self._nodemap_changed 54 | ) 55 | 56 | def _nodemap_changed(self, data, stat): 57 | """Called when the nodemap changes.""" 58 | 59 | if not stat: 60 | raise EnvironmentNotFoundException(self.nodemap_path) 61 | 62 | try: 63 | conf_path = self._deserialize_nodemap(data)[self.hostname] 64 | except KeyError: 65 | conf_path = '/services/%s/conf' % self.service 66 | 67 | self.config_watcher = DataWatch( 68 | self.zk, conf_path, 69 | self._config_changed 70 | ) 71 | 72 | def _config_changed(self, data, stat): 73 | """Called when config changes.""" 74 | 75 | self.config = json.loads(data) 76 | 77 | if self.cb: 78 | self.cb(self.config) 79 | 80 | @staticmethod 81 | def _deserialize_nodemap(d): 82 | if not len(d): 83 | return {} 84 | return json.loads(d) 85 | 86 | def __getitem__(self, key): 87 | return self.config[key] 88 | 89 | def __iter__(self): 90 | return iter(self.config) 91 | 92 | def __len__(self): 93 | return len(self.config) 94 | -------------------------------------------------------------------------------- /jones/jones.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | 15 | from functools import partial 16 | import collections 17 | import json 18 | import zkutil 19 | 20 | 21 | class ZNodeMap(object): 22 | """Associate znodes with names.""" 23 | 24 | OLD_SEPARATOR = ' -> ' 25 | 26 | def __init__(self, zk, path): 27 | """ 28 | zk: KazooClient instance 29 | path: znode to store associations 30 | """ 31 | self.zk = zk 32 | self.path = path 33 | 34 | zk.ensure_path(path) 35 | 36 | def set(self, name, dest): 37 | zmap, version = self._get() 38 | zmap[name] = dest 39 | self._set(zmap, version) 40 | 41 | def get(self, name): 42 | return self.get_all()[name] 43 | 44 | def get_all(self): 45 | """returns a map of names to destinations.""" 46 | 47 | zmap, v = self._get() 48 | return zmap 49 | 50 | def delete(self, name): 51 | zmap, version = self._get() 52 | del zmap[name] 53 | self._set(zmap, version) 54 | 55 | def _get(self): 56 | """get and parse data stored in self.path.""" 57 | 58 | data, stat = self.zk.get(self.path) 59 | if not len(data): 60 | return {}, stat.version 61 | if self.OLD_SEPARATOR in data: 62 | return self._get_old() 63 | return json.loads(data), stat.version 64 | 65 | def _set(self, data, version): 66 | """serialize and set data to self.path.""" 67 | 68 | self.zk.set(self.path, json.dumps(data), version) 69 | 70 | def _get_old(self): 71 | """get and parse data stored in self.path.""" 72 | 73 | def _deserialize(d): 74 | if not len(d): 75 | return {} 76 | return dict(l.split(self.OLD_SEPARATOR) for l in d.split('\n')) 77 | 78 | data, stat = self.zk.get(self.path) 79 | return _deserialize(data.decode('utf8')), stat.version 80 | 81 | 82 | class Env(unicode): 83 | def __new__(cls, name): 84 | if not name: 85 | empty = True 86 | name = '' 87 | else: 88 | assert name[0] != '/' 89 | empty = False 90 | s = unicode.__new__(cls, name) 91 | s._empty = empty 92 | return s 93 | 94 | @property 95 | def is_root(self): 96 | return self._empty 97 | 98 | @property 99 | def components(self): 100 | if self.is_root: 101 | return [''] 102 | else: 103 | return self.split('/') 104 | 105 | Env.Root = Env(None) 106 | 107 | 108 | class Jones(object): 109 | """ 110 | 111 | Glossary: 112 | view 113 | refers to a node which has has the following algorithm applied 114 | for node in root -> env 115 | update view with node.config 116 | environment 117 | a node in the service graph 118 | as passed to get/set config, it should identify 119 | the node within the service 120 | i.e. "production" or "dev/mwhooker" 121 | """ 122 | 123 | def __init__(self, service, zk): 124 | self.zk = zk 125 | self.service = service 126 | self.root = "/services/%s" % service 127 | self.conf_path = "%s/conf" % self.root 128 | self.view_path = "%s/views" % self.root 129 | self.associations = ZNodeMap(zk, "%s/nodemaps" % self.root) 130 | 131 | self._get_env_path = partial(self._get_path_by_env, self.conf_path) 132 | self._get_view_path = partial(self._get_path_by_env, self.view_path) 133 | 134 | def create_config(self, env, conf): 135 | """ 136 | Set conf to env under service. 137 | 138 | pass None to env for root. 139 | """ 140 | 141 | if not isinstance(conf, collections.Mapping): 142 | raise ValueError("conf must be a collections.Mapping") 143 | 144 | self.zk.ensure_path(self.view_path) 145 | 146 | self._create( 147 | self._get_env_path(env), 148 | conf 149 | ) 150 | 151 | self._update_view(env) 152 | 153 | def set_config(self, env, conf, version): 154 | """ 155 | Set conf to env under service. 156 | 157 | pass None to env for root. 158 | """ 159 | 160 | if not isinstance(conf, collections.Mapping): 161 | raise ValueError("conf must be a collections.Mapping") 162 | 163 | self._set( 164 | self._get_env_path(env), 165 | conf, 166 | version 167 | ) 168 | path = self._get_env_path(env) 169 | """Update env's children with new config.""" 170 | for child in zkutil.walk(self.zk, path): 171 | self._update_view(Env(child[len(self.conf_path)+1:])) 172 | 173 | def delete_config(self, env, version): 174 | self.zk.delete( 175 | self._get_env_path(env), 176 | version 177 | ) 178 | 179 | self.zk.delete( 180 | self._get_view_path(env) 181 | ) 182 | 183 | def get_config(self, hostname): 184 | """ 185 | Returns a configuration for hostname. 186 | 187 | """ 188 | version, config = self._get( 189 | self.associations.get(hostname) 190 | ) 191 | return config 192 | 193 | def get_config_by_env(self, env): 194 | """ 195 | Get the config dictionary by `env`. 196 | 197 | Returns a 2-tuple like (version, data). 198 | 199 | """ 200 | return self._get( 201 | self._get_env_path(env) 202 | ) 203 | 204 | def get_view_by_env(self, env): 205 | """ 206 | Returns the view of `env`. 207 | 208 | """ 209 | version, data = self._get(self._get_view_path(env)) 210 | return data 211 | 212 | def assoc_host(self, hostname, env): 213 | """ 214 | Associate a host with an environment. 215 | 216 | hostname is opaque to Jones. 217 | Any string which uniquely identifies a host is acceptable. 218 | """ 219 | 220 | dest = self._get_view_path(env) 221 | self.associations.set(hostname, dest) 222 | 223 | def get_associations(self, env): 224 | """ 225 | Get all the associations for this env. 226 | 227 | Root cannot have associations, so return None for root. 228 | 229 | returns a map of hostnames to environments. 230 | """ 231 | 232 | if env.is_root: 233 | return None 234 | 235 | associations = self.associations.get_all() 236 | return [assoc for assoc in associations 237 | if associations[assoc] == self._get_view_path(env)] 238 | 239 | def delete_association(self, hostname): 240 | self.associations.delete(hostname) 241 | 242 | def exists(self): 243 | """Does this service exist in zookeeper""" 244 | 245 | return self.zk.exists( 246 | self._get_env_path(Env.Root) 247 | ) 248 | 249 | def delete_all(self): 250 | self.zk.delete(self.root, recursive=True) 251 | 252 | def get_child_envs(self, env): 253 | prefix = self._get_env_path(env) 254 | envs = zkutil.walk(self.zk, prefix) 255 | return map(lambda e: e[len(prefix)+1:], envs) 256 | 257 | def _flatten_from_root(self, env): 258 | """ 259 | Flatten values from root down in to new view. 260 | """ 261 | 262 | nodes = env.components 263 | 264 | # Path through the znode graph from root ('') to env 265 | path = [nodes[:n] for n in xrange(len(nodes) + 1)] 266 | 267 | # Expand path and map it to the root 268 | path = map( 269 | self._get_env_path, 270 | [Env('/'.join(p)) for p in path] 271 | ) 272 | 273 | data = {} 274 | for n in path: 275 | _, config = self._get(n) 276 | data.update(config) 277 | 278 | return data 279 | 280 | def _update_view(self, env): 281 | 282 | dest = self._get_view_path(env) 283 | if not self.zk.exists(dest): 284 | self.zk.ensure_path(dest) 285 | 286 | self._set(dest, self._flatten_from_root(env)) 287 | 288 | def _get_path_by_env(self, prefix, env): 289 | if env.is_root: 290 | return prefix 291 | return '/'.join((prefix, env)) 292 | 293 | def _get_nodemap_path(self, hostname): 294 | return "%s/%s" % (self.nodemap_path, hostname) 295 | 296 | def _get(self, path): 297 | data, metadata = self.zk.get(path) 298 | return metadata.version, json.loads(data) 299 | 300 | def _set(self, path, data, *args, **kwargs): 301 | return self.zk.set(path, json.dumps(data), *args, **kwargs) 302 | 303 | def _create(self, path, data, *args, **kwargs): 304 | return self.zk.create(path, json.dumps(data), *args, **kwargs) 305 | -------------------------------------------------------------------------------- /jones/jonesconfig.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | 15 | import os 16 | 17 | _basedir = os.path.abspath(os.path.dirname(__file__)) 18 | 19 | DEBUG = True 20 | TESTING = True 21 | SECRET_KEY = 'developement key' 22 | ZK_CONNECTION_STRING = '127.0.0.1:2181' 23 | ZK_DIGEST_PASSWORD = 'changeme' 24 | -------------------------------------------------------------------------------- /jones/static/css/global.css: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | body { 16 | padding-top: 50px; 17 | } 18 | -------------------------------------------------------------------------------- /jones/static/css/jsoneditor.css: -------------------------------------------------------------------------------- 1 | 2 | .jsoneditor-field, .jsoneditor-value, .jsoneditor-field-readonly, .jsoneditor-readonly { 3 | border: 1px solid transparent; 4 | min-height: 16px; 5 | min-width: 24px; 6 | padding: 2px; 7 | margin: 1px; 8 | outline: none; 9 | word-wrap: break-word; 10 | } 11 | 12 | .jsoneditor-empty { 13 | background-color: #E5E5E5; 14 | } 15 | 16 | .jsoneditor-separator { 17 | padding: 3px 0; 18 | vertical-align: top; 19 | } 20 | 21 | .jsoneditor-value:focus, .jsoneditor-field:focus, 22 | .jsoneditor-value:hover, .jsoneditor-field:hover { 23 | background-color: #FFFFAB; 24 | border: 1px solid yellow; 25 | } 26 | 27 | .jsoneditor-field-readonly:hover { 28 | border: 1px solid white; 29 | } 30 | 31 | .jsoneditor-readonly { 32 | color: gray; 33 | } 34 | 35 | button.jsoneditor-collapsed { 36 | background: url('/static/img/jsoneditor/treeRightTriangleBlack.png') no-repeat center; 37 | } 38 | 39 | button.jsoneditor-expanded { 40 | background: url('/static/img/jsoneditor/treeDownTriangleBlack.png') no-repeat center; 41 | } 42 | 43 | button.jsoneditor-invisible { 44 | visibility: hidden; 45 | background: none; 46 | } 47 | 48 | button.jsoneditor-remove, button.jsoneditor-append, button.jsoneditor-duplicate, 49 | button.jsoneditor-collapsed, button.jsoneditor-expanded, 50 | button.jsoneditor-invisible, button.jsoneditor-dragarea, 51 | button.jsoneditor-type-auto, button.jsoneditor-type-string, 52 | button.jsoneditor-type-array, button.jsoneditor-type-object { 53 | width: 24px; 54 | height: 24px; 55 | padding: 0; 56 | margin: 0; 57 | border: none; 58 | cursor: pointer; 59 | } 60 | 61 | button.jsoneditor-collapsed, button.jsoneditor-expanded, 62 | button.jsoneditor-invisible { 63 | float: left; 64 | } 65 | 66 | button.jsoneditor-remove { 67 | background: url('/static/img/jsoneditor/delete_gray.png') no-repeat center; 68 | } 69 | button.jsoneditor-remove:hover { 70 | background: url('/static/img/jsoneditor/delete_red.png') no-repeat center; 71 | } 72 | 73 | button.jsoneditor-append { 74 | background: url('/static/img/jsoneditor/add_gray.png') no-repeat center; 75 | } 76 | button.jsoneditor-append:hover { 77 | background: url('/static/img/jsoneditor/add_green.png') no-repeat center; 78 | } 79 | 80 | button.jsoneditor-duplicate { 81 | background: url('/static/img/jsoneditor/duplicate_gray.png') no-repeat center; 82 | } 83 | button.jsoneditor-duplicate:hover { 84 | background: url('/static/img/jsoneditor/duplicate_blue.png') no-repeat center; 85 | } 86 | 87 | button.jsoneditor-type-string { 88 | background: url('/static/img/jsoneditor/string_gray.png') no-repeat center; 89 | } 90 | button.jsoneditor-type-string:hover { 91 | background: url('/static/img/jsoneditor/string_blue.png') no-repeat center; 92 | } 93 | 94 | button.jsoneditor-type-auto { 95 | background: url('/static/img/jsoneditor/auto_gray.png') no-repeat center; 96 | } 97 | button.jsoneditor-type-auto:hover { 98 | background: url('/static/img/jsoneditor/auto_blue.png') no-repeat center; 99 | } 100 | 101 | button.jsoneditor-type-object { 102 | background: url('/static/img/jsoneditor/object_gray.png') no-repeat center; 103 | } 104 | button.jsoneditor-type-object:hover { 105 | background: url('/static/img/jsoneditor/object_blue.png') no-repeat center; 106 | } 107 | 108 | button.jsoneditor-type-array { 109 | background: url('/static/img/jsoneditor/array_gray.png') no-repeat center; 110 | } 111 | button.jsoneditor-type-array:hover { 112 | background: url('/static/img/jsoneditor/array_blue.png') no-repeat center; 113 | } 114 | 115 | div.jsoneditor-select { 116 | border: 1px solid gray; 117 | background-color: white; 118 | box-shadow: 4px 4px 10px lightgray; 119 | } 120 | 121 | div.jsoneditor-option { 122 | color: #4D4D4D; 123 | background-color: white; 124 | 125 | border: none; 126 | margin: 0; 127 | display: block; 128 | text-align: left; 129 | cursor: pointer; 130 | } 131 | div.jsoneditor-option:hover { 132 | background-color: #FFFFAB; 133 | color: black; 134 | } 135 | div.jsoneditor-option-string, div.jsoneditor-option-auto, 136 | div.jsoneditor-option-object, div.jsoneditor-option-array { 137 | padding: 4px 12px 4px 24px; 138 | background-repeat:no-repeat; 139 | background-position: 4px center; 140 | } 141 | div.jsoneditor-option-string { 142 | background-image: url('/static/img/jsoneditor/string_blue.png'); 143 | } 144 | div.jsoneditor-option-auto { 145 | background-image: url('/static/img/jsoneditor/auto_blue.png'); 146 | } 147 | div.jsoneditor-option-object { 148 | background-image: url('/static/img/jsoneditor/object_blue.png'); 149 | } 150 | div.jsoneditor-option-array { 151 | background-image: url('/static/img/jsoneditor/array_blue.png'); 152 | } 153 | div.jsoneditor-option-selected { 154 | background-color: #D5DDF6; 155 | } 156 | 157 | div.jsoneditor-frame { 158 | color: #1A1A1A; 159 | border: 1px solid #97B0F8; 160 | width: 100%; 161 | height: 100%; 162 | overflow: auto; 163 | position: relative; 164 | } 165 | 166 | table.jsoneditor-table { 167 | border-collapse: collapse; 168 | border-spacing: 0; 169 | width: 100%; 170 | } 171 | 172 | div.jsoneditor-content-outer, div.jsonformatter-content { 173 | width: 100%; 174 | height: 100%; 175 | margin: -32px 0 0 0; 176 | padding: 32px 0 0 0; 177 | 178 | -moz-box-sizing: border-box; 179 | -webkit-box-sizing: border-box; 180 | box-sizing: border-box; 181 | 182 | overflow: hidden; 183 | } 184 | 185 | div.jsoneditor-content { 186 | width: 100%; 187 | height: 100%; 188 | position: relative; 189 | overflow: auto; 190 | } 191 | 192 | textarea.jsonformatter-textarea { 193 | width: 100%; 194 | height: 100%; 195 | margin: 0; 196 | 197 | -moz-box-sizing: border-box; 198 | -webkit-box-sizing: border-box; 199 | box-sizing: border-box; 200 | 201 | border: none; 202 | background-color: white; 203 | resize: none; 204 | } 205 | 206 | /* 207 | td.jsoneditor-td-drag { 208 | vertical-align: middle; 209 | } 210 | */ 211 | tr.jsoneditor-tr-highlight { 212 | background-color: #FFFFAB; 213 | } 214 | 215 | button.jsoneditor-dragarea { 216 | width: 16px; 217 | height: 16px; 218 | margin: 3px 0; 219 | background: url('/static/img/jsoneditor/dots_gray.gif') top center; 220 | background-repeat: repeat-y; 221 | display: block; 222 | cursor: move; 223 | } 224 | 225 | table.jsoneditor-menu { 226 | width: 100%; 227 | height: 32px; 228 | left: 0; 229 | top: 0; 230 | border-collapse: collapse; 231 | } 232 | 233 | td.jsoneditor-menu { 234 | font-weight: bold; 235 | background-color: #D5DDF6; 236 | border-bottom: 1px solid #97B0F8; 237 | text-align: left; 238 | padding: 0 3px; 239 | } 240 | 241 | tr, th, td { 242 | padding: 0; 243 | margin: 0; 244 | } 245 | 246 | td.jsoneditor-td { 247 | vertical-align: top; 248 | } 249 | 250 | td.jsoneditor-td { 251 | padding: 0 3px; 252 | } 253 | 254 | td.jsoneditor-td-edit { 255 | background-color: #F5F5F5; 256 | padding: 0; 257 | } 258 | 259 | td.jsoneditor-td-tree { 260 | vertical-align: top; 261 | } 262 | 263 | td.jsoneditor-droparea { 264 | height: 24px; 265 | 266 | border-top: 1px dashed gray; 267 | border-bottom: 1px dashed gray; 268 | background-color: #FFFF80; 269 | } 270 | 271 | .jsoneditor-field, .jsoneditor-value, .jsoneditor-td, .jsoneditor-th, 272 | .jsoneditor-type, 273 | .jsonformatter-textarea { 274 | font-family: droid sans mono, monospace, courier new, courier, sans-serif; 275 | font-size: 10pt; 276 | } 277 | -------------------------------------------------------------------------------- /jones/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/favicon.ico -------------------------------------------------------------------------------- /jones/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /jones/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/add_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/add_gray.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/add_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/add_green.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/array_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/array_blue.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/array_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/array_gray.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/auto_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/auto_blue.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/auto_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/auto_gray.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/delete_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/delete_gray.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/delete_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/delete_red.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/dots_blue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/dots_blue.gif -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/dots_blue.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/dots_blue.xcf -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/dots_gray.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/dots_gray.gif -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/dots_gray.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/dots_gray.xcf -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/dots_lightgray.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/dots_lightgray.gif -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/dots_lightgray.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/dots_lightgray.xcf -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/duplicate_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/duplicate_blue.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/duplicate_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/duplicate_gray.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/empty_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/empty_blue.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/empty_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/empty_gray.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/images.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 86 | 87 | 88 | 89 | 90 |
+

91 |
+

92 | 93 | 94 |
×

95 |
×

96 | 97 | 98 |

99 |

100 | 101 | 102 |
A

103 |
A

104 | 105 | 106 |
{ }

107 |
{ }

108 | 109 | 110 |
[ ]

111 |
[ ]

112 | 113 | 114 |
"

115 |
"

116 | 117 | 118 |

119 |

120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/license.txt: -------------------------------------------------------------------------------- 1 | The treeDown and treeRight icons are copied from the Chromium Developer Tools. 2 | -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/object_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/object_blue.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/object_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/object_gray.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/string_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/string_blue.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/string_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/string_gray.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/treeDownTriangleBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/treeDownTriangleBlack.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/treeLeftTriangleBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/treeLeftTriangleBlack.png -------------------------------------------------------------------------------- /jones/static/img/jsoneditor/treeRightTriangleBlack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/img/jsoneditor/treeRightTriangleBlack.png -------------------------------------------------------------------------------- /jones/static/js/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2011-10-19 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, regexp: true */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | var JSON; 163 | if (!JSON) { 164 | JSON = {}; 165 | } 166 | 167 | (function () { 168 | 'use strict'; 169 | 170 | function f(n) { 171 | // Format integers to have at least two digits. 172 | return n < 10 ? '0' + n : n; 173 | } 174 | 175 | if (typeof Date.prototype.toJSON !== 'function') { 176 | 177 | Date.prototype.toJSON = function (key) { 178 | 179 | return isFinite(this.valueOf()) 180 | ? this.getUTCFullYear() + '-' + 181 | f(this.getUTCMonth() + 1) + '-' + 182 | f(this.getUTCDate()) + 'T' + 183 | f(this.getUTCHours()) + ':' + 184 | f(this.getUTCMinutes()) + ':' + 185 | f(this.getUTCSeconds()) + 'Z' 186 | : null; 187 | }; 188 | 189 | String.prototype.toJSON = 190 | Number.prototype.toJSON = 191 | Boolean.prototype.toJSON = function (key) { 192 | return this.valueOf(); 193 | }; 194 | } 195 | 196 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 198 | gap, 199 | indent, 200 | meta = { // table of character substitutions 201 | '\b': '\\b', 202 | '\t': '\\t', 203 | '\n': '\\n', 204 | '\f': '\\f', 205 | '\r': '\\r', 206 | '"' : '\\"', 207 | '\\': '\\\\' 208 | }, 209 | rep; 210 | 211 | 212 | function quote(string) { 213 | 214 | // If the string contains no control characters, no quote characters, and no 215 | // backslash characters, then we can safely slap some quotes around it. 216 | // Otherwise we must also replace the offending characters with safe escape 217 | // sequences. 218 | 219 | escapable.lastIndex = 0; 220 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 221 | var c = meta[a]; 222 | return typeof c === 'string' 223 | ? c 224 | : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 225 | }) + '"' : '"' + string + '"'; 226 | } 227 | 228 | 229 | function str(key, holder) { 230 | 231 | // Produce a string from holder[key]. 232 | 233 | var i, // The loop counter. 234 | k, // The member key. 235 | v, // The member value. 236 | length, 237 | mind = gap, 238 | partial, 239 | value = holder[key]; 240 | 241 | // If the value has a toJSON method, call it to obtain a replacement value. 242 | 243 | if (value && typeof value === 'object' && 244 | typeof value.toJSON === 'function') { 245 | value = value.toJSON(key); 246 | } 247 | 248 | // If we were called with a replacer function, then call the replacer to 249 | // obtain a replacement value. 250 | 251 | if (typeof rep === 'function') { 252 | value = rep.call(holder, key, value); 253 | } 254 | 255 | // What happens next depends on the value's type. 256 | 257 | switch (typeof value) { 258 | case 'string': 259 | return quote(value); 260 | 261 | case 'number': 262 | 263 | // JSON numbers must be finite. Encode non-finite numbers as null. 264 | 265 | return isFinite(value) ? String(value) : 'null'; 266 | 267 | case 'boolean': 268 | case 'null': 269 | 270 | // If the value is a boolean or null, convert it to a string. Note: 271 | // typeof null does not produce 'null'. The case is included here in 272 | // the remote chance that this gets fixed someday. 273 | 274 | return String(value); 275 | 276 | // If the type is 'object', we might be dealing with an object or an array or 277 | // null. 278 | 279 | case 'object': 280 | 281 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 282 | // so watch out for that case. 283 | 284 | if (!value) { 285 | return 'null'; 286 | } 287 | 288 | // Make an array to hold the partial results of stringifying this object value. 289 | 290 | gap += indent; 291 | partial = []; 292 | 293 | // Is the value an array? 294 | 295 | if (Object.prototype.toString.apply(value) === '[object Array]') { 296 | 297 | // The value is an array. Stringify every element. Use null as a placeholder 298 | // for non-JSON values. 299 | 300 | length = value.length; 301 | for (i = 0; i < length; i += 1) { 302 | partial[i] = str(i, value) || 'null'; 303 | } 304 | 305 | // Join all of the elements together, separated with commas, and wrap them in 306 | // brackets. 307 | 308 | v = partial.length === 0 309 | ? '[]' 310 | : gap 311 | ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' 312 | : '[' + partial.join(',') + ']'; 313 | gap = mind; 314 | return v; 315 | } 316 | 317 | // If the replacer is an array, use it to select the members to be stringified. 318 | 319 | if (rep && typeof rep === 'object') { 320 | length = rep.length; 321 | for (i = 0; i < length; i += 1) { 322 | if (typeof rep[i] === 'string') { 323 | k = rep[i]; 324 | v = str(k, value); 325 | if (v) { 326 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 327 | } 328 | } 329 | } 330 | } else { 331 | 332 | // Otherwise, iterate through all of the keys in the object. 333 | 334 | for (k in value) { 335 | if (Object.prototype.hasOwnProperty.call(value, k)) { 336 | v = str(k, value); 337 | if (v) { 338 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 339 | } 340 | } 341 | } 342 | } 343 | 344 | // Join all of the member texts together, separated with commas, 345 | // and wrap them in braces. 346 | 347 | v = partial.length === 0 348 | ? '{}' 349 | : gap 350 | ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' 351 | : '{' + partial.join(',') + '}'; 352 | gap = mind; 353 | return v; 354 | } 355 | } 356 | 357 | // If the JSON object does not yet have a stringify method, give it one. 358 | 359 | if (typeof JSON.stringify !== 'function') { 360 | JSON.stringify = function (value, replacer, space) { 361 | 362 | // The stringify method takes a value and an optional replacer, and an optional 363 | // space parameter, and returns a JSON text. The replacer can be a function 364 | // that can replace values, or an array of strings that will select the keys. 365 | // A default replacer method can be provided. Use of the space parameter can 366 | // produce text that is more easily readable. 367 | 368 | var i; 369 | gap = ''; 370 | indent = ''; 371 | 372 | // If the space parameter is a number, make an indent string containing that 373 | // many spaces. 374 | 375 | if (typeof space === 'number') { 376 | for (i = 0; i < space; i += 1) { 377 | indent += ' '; 378 | } 379 | 380 | // If the space parameter is a string, it will be used as the indent string. 381 | 382 | } else if (typeof space === 'string') { 383 | indent = space; 384 | } 385 | 386 | // If there is a replacer, it must be a function or an array. 387 | // Otherwise, throw an error. 388 | 389 | rep = replacer; 390 | if (replacer && typeof replacer !== 'function' && 391 | (typeof replacer !== 'object' || 392 | typeof replacer.length !== 'number')) { 393 | throw new Error('JSON.stringify'); 394 | } 395 | 396 | // Make a fake root object containing our value under the key of ''. 397 | // Return the result of stringifying the value. 398 | 399 | return str('', {'': value}); 400 | }; 401 | } 402 | 403 | 404 | // If the JSON object does not yet have a parse method, give it one. 405 | 406 | if (typeof JSON.parse !== 'function') { 407 | JSON.parse = function (text, reviver) { 408 | 409 | // The parse method takes a text and an optional reviver function, and returns 410 | // a JavaScript value if the text is a valid JSON text. 411 | 412 | var j; 413 | 414 | function walk(holder, key) { 415 | 416 | // The walk method is used to recursively walk the resulting structure so 417 | // that modifications can be made. 418 | 419 | var k, v, value = holder[key]; 420 | if (value && typeof value === 'object') { 421 | for (k in value) { 422 | if (Object.prototype.hasOwnProperty.call(value, k)) { 423 | v = walk(value, k); 424 | if (v !== undefined) { 425 | value[k] = v; 426 | } else { 427 | delete value[k]; 428 | } 429 | } 430 | } 431 | } 432 | return reviver.call(holder, key, value); 433 | } 434 | 435 | 436 | // Parsing happens in four stages. In the first stage, we replace certain 437 | // Unicode characters with escape sequences. JavaScript handles many characters 438 | // incorrectly, either silently deleting them, or treating them as line endings. 439 | 440 | text = String(text); 441 | cx.lastIndex = 0; 442 | if (cx.test(text)) { 443 | text = text.replace(cx, function (a) { 444 | return '\\u' + 445 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 446 | }); 447 | } 448 | 449 | // In the second stage, we run the text against regular expressions that look 450 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 451 | // because they can cause invocation, and '=' because it can cause mutation. 452 | // But just to be safe, we want to reject all unexpected forms. 453 | 454 | // We split the second stage into 4 regexp operations in order to work around 455 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 456 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 457 | // replace all simple value tokens with ']' characters. Third, we delete all 458 | // open brackets that follow a colon or comma or that begin the text. Finally, 459 | // we look to see that the remaining characters are only whitespace or ']' or 460 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 461 | 462 | if (/^[\],:{}\s]*$/ 463 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 464 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 465 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 466 | 467 | // In the third stage we use the eval function to compile the text into a 468 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 469 | // in JavaScript: it can begin a block or an object literal. We wrap the text 470 | // in parens to eliminate the ambiguity. 471 | 472 | j = eval('(' + text + ')'); 473 | 474 | // In the optional fourth stage, we recursively walk the new structure, passing 475 | // each name/value pair to a reviver function for possible transformation. 476 | 477 | return typeof reviver === 'function' 478 | ? walk({'': j}, '') 479 | : j; 480 | } 481 | 482 | // If the text is not JSON parseable, then a SyntaxError is thrown. 483 | 484 | throw new SyntaxError('JSON.parse'); 485 | }; 486 | } 487 | }()); 488 | -------------------------------------------------------------------------------- /jones/static/js/service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | */ 14 | 15 | 16 | $(function() { 17 | window.editor = new JSONEditor($('#jsoneditor')[0]); 18 | window.editor.set(config); 19 | 20 | $('#update').click(function() { 21 | $.ajax({ 22 | url: window.location.href, 23 | data: { 24 | data: JSON.stringify(editor.get()), 25 | version: version 26 | }, 27 | type: 'put', 28 | success: function() { 29 | window.location.reload(true); 30 | } 31 | }); 32 | }); 33 | 34 | $('.add-env').click(function() { 35 | var form = $('#addChildModal form'); 36 | var env = $(this).data('env'); 37 | var env_components = _.reject(env.split('/'), _.isEmpty) 38 | 39 | $('#addChildModal .modal-header h4').text(env); 40 | $('#addChildModal').modal(); 41 | 42 | $('input', form).bind( 43 | "propertychange keyup input paste", function(event){ 44 | validate(env_components, $(this).val(), function(path) { 45 | $('#addChildModal .modal-header h4').text(path); 46 | }); 47 | }); 48 | 49 | form.submit(function() { 50 | return validate(env_components, $('input', this).val(), function(path) { 51 | form.attr('action', path); 52 | }); 53 | }); 54 | 55 | return false; 56 | }); 57 | 58 | $('#modalSubmit').click(function() { 59 | $('#addChildModal form').submit(); 60 | }); 61 | 62 | // TODO: confirm delete. 63 | $('.del-env').click(function() { 64 | var href = $(this).attr('href'); 65 | 66 | $.ajax({ 67 | url: href, 68 | type: 'delete', 69 | success: function() { 70 | window.location.reload(true); 71 | } 72 | }); 73 | return false; 74 | }); 75 | 76 | $("#associations > .btn").click(function() { 77 | // AJAX here because of the http semantics. 78 | $(this).hide(); 79 | 80 | $('#add-assoc').removeClass('hidden'); 81 | $('#add-assoc .btn').click(function() { 82 | validate('service', service, 'association', 83 | $('#add-assoc input').val(), function(path) { 84 | $(this).button('loading'); 85 | $.ajax({ 86 | url: path.slice(0,-1), 87 | data: {env: env}, 88 | type: 'put', 89 | success: function() { 90 | window.location.reload(true); 91 | } 92 | }); 93 | }); 94 | }); 95 | return false; 96 | }); 97 | 98 | $('#associations .del-assoc').click(function() { 99 | validate('service', service, 'association', 100 | $(this).data('hostname'), function(path) { 101 | $(this).button('loading'); 102 | 103 | $.ajax({ 104 | url: path.slice(0, -1), 105 | type: 'delete', 106 | success: function(data) { 107 | window.location.reload(true); 108 | } 109 | }); 110 | }); 111 | return false; 112 | }); 113 | 114 | /* 115 | window.formatter = new JSONFormatter($('#jsonformatter')[0]); 116 | window.formatter.set(config); 117 | window.formatter.onError = console.log 118 | */ 119 | }); 120 | -------------------------------------------------------------------------------- /jones/static/js/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.6.0 2 | // http://underscorejs.org 3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.6.0";var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&"constructor"in n&&"constructor"in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};T.unescape=j.invert(T.escape);var I={escape:new RegExp("["+j.keys(T.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(T.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),"function"==typeof define&&define.amd&&define("underscore",[],function(){return j})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /jones/static/prettify/lang-apollo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-apollo.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-clj.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | var a=null; 17 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^[([{]+/,a,"([{"],["clo",/^[)\]}]+/,a,")]}"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\b/,a], 18 | ["typ",/^:[\dA-Za-z-]+/]]),["clj"]); 19 | -------------------------------------------------------------------------------- /jones/static/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /jones/static/prettify/lang-go.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-go.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-hs.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t-\r ]+/,null,"\t\n \r "],["str",/^"(?:[^\n\f\r"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["str",/^'(?:[^\n\f\r'\\]|\\[^&])'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:--+[^\n\f\r]*|{-(?:[^-]|-+[^}-])*-})/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^\d'A-Za-z]|$)/, 2 | null],["pln",/^(?:[A-Z][\w']*\.)*[A-Za-z][\w']*/],["pun",/^[^\d\t-\r "'A-Za-z]+/]]),["hs"]); 3 | -------------------------------------------------------------------------------- /jones/static/prettify/lang-lisp.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,a,"("],["clo",/^\)+/,a,")"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/,a], 3 | ["lit",/^[+-]?(?:[#0]x[\da-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[de][+-]?\d+)?)/i],["lit",/^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/],["pln",/^-*(?:[_a-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/i],["pun",/^[^\w\t\n\r "'-);\\\xa0]+/]]),["cl","el","lisp","scm"]); 4 | -------------------------------------------------------------------------------- /jones/static/prettify/lang-lua.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-lua.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-ml.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-ml.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-n.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:'(?:[^\n\r'\\]|\\.)*'|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,a,'"'],["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,a,"#"],["pln",/^\s+/,a," \r\n\t\xa0"]],[["str",/^@"(?:[^"]|"")*(?:"|$)/,a],["str",/^<#[^#>]*(?:#>|$)/,a],["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,a],["com",/^\/\/[^\n\r]*/,a],["com",/^\/\*[\S\s]*?(?:\*\/|$)/, 3 | a],["kwd",/^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\b/, 4 | a],["typ",/^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\b/,a],["lit",/^@[$_a-z][\w$@]*/i,a],["typ",/^@[A-Z]+[a-z][\w$@]*/,a],["pln",/^'?[$_a-z][\w$@]*/i,a],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,a,"0123456789"],["pun",/^.[^\s\w"-$'./@`]*/,a]]),["n","nemerle"]); 5 | -------------------------------------------------------------------------------- /jones/static/prettify/lang-proto.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.sourceDecorator({keywords:"bytes,default,double,enum,extend,extensions,false,group,import,max,message,option,optional,package,repeated,required,returns,rpc,service,syntax,to,true",types:/^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\b/,cStyleComments:!0}),["proto"]); 2 | -------------------------------------------------------------------------------- /jones/static/prettify/lang-scala.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-scala.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-sql.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-sql.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-tex.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-tex.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-vb.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-vb.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-vhdl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-vhdl.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-wiki.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwhooker/jones/121e89572ca063f456b8e94cbb8cbee26c307a8f/jones/static/prettify/lang-wiki.js -------------------------------------------------------------------------------- /jones/static/prettify/lang-xq.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["var pln",/^\$[\w-]+/,null,"$"]],[["pln",/^[\s=][<>][\s=]/],["lit",/^@[\w-]+/],["tag",/^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["com",/^\(:[\S\s]*?:\)/],["pln",/^[(),/;[\]{}]$/],["str",/^(?:"(?:[^"\\{]|\\[\S\s])*(?:"|$)|'(?:[^'\\{]|\\[\S\s])*(?:'|$))/,null,"\"'"],["kwd",/^(?:xquery|where|version|variable|union|typeswitch|treat|to|then|text|stable|sortby|some|self|schema|satisfies|returns|return|ref|processing-instruction|preceding-sibling|preceding|precedes|parent|only|of|node|namespace|module|let|item|intersect|instance|in|import|if|function|for|follows|following-sibling|following|external|except|every|else|element|descending|descendant-or-self|descendant|define|default|declare|comment|child|cast|case|before|attribute|assert|ascending|as|ancestor-or-self|ancestor|after|eq|order|by|or|and|schema-element|document-node|node|at)\b/], 2 | ["typ",/^(?:xs:yearMonthDuration|xs:unsignedLong|xs:time|xs:string|xs:short|xs:QName|xs:Name|xs:long|xs:integer|xs:int|xs:gYearMonth|xs:gYear|xs:gMonthDay|xs:gDay|xs:float|xs:duration|xs:double|xs:decimal|xs:dayTimeDuration|xs:dateTime|xs:date|xs:byte|xs:boolean|xs:anyURI|xf:yearMonthDuration)\b/,null],["fun pln",/^(?:xp:dereference|xinc:node-expand|xinc:link-references|xinc:link-expand|xhtml:restructure|xhtml:clean|xhtml:add-lists|xdmp:zip-manifest|xdmp:zip-get|xdmp:zip-create|xdmp:xquery-version|xdmp:word-convert|xdmp:with-namespaces|xdmp:version|xdmp:value|xdmp:user-roles|xdmp:user-last-login|xdmp:user|xdmp:url-encode|xdmp:url-decode|xdmp:uri-is-file|xdmp:uri-format|xdmp:uri-content-type|xdmp:unquote|xdmp:unpath|xdmp:triggers-database|xdmp:trace|xdmp:to-json|xdmp:tidy|xdmp:subbinary|xdmp:strftime|xdmp:spawn-in|xdmp:spawn|xdmp:sleep|xdmp:shutdown|xdmp:set-session-field|xdmp:set-response-encoding|xdmp:set-response-content-type|xdmp:set-response-code|xdmp:set-request-time-limit|xdmp:set|xdmp:servers|xdmp:server-status|xdmp:server-name|xdmp:server|xdmp:security-database|xdmp:security-assert|xdmp:schema-database|xdmp:save|xdmp:role-roles|xdmp:role|xdmp:rethrow|xdmp:restart|xdmp:request-timestamp|xdmp:request-status|xdmp:request-cancel|xdmp:request|xdmp:redirect-response|xdmp:random|xdmp:quote|xdmp:query-trace|xdmp:query-meters|xdmp:product-edition|xdmp:privilege-roles|xdmp:privilege|xdmp:pretty-print|xdmp:powerpoint-convert|xdmp:platform|xdmp:permission|xdmp:pdf-convert|xdmp:path|xdmp:octal-to-integer|xdmp:node-uri|xdmp:node-replace|xdmp:node-kind|xdmp:node-insert-child|xdmp:node-insert-before|xdmp:node-insert-after|xdmp:node-delete|xdmp:node-database|xdmp:mul64|xdmp:modules-root|xdmp:modules-database|xdmp:merging|xdmp:merge-cancel|xdmp:merge|xdmp:md5|xdmp:logout|xdmp:login|xdmp:log-level|xdmp:log|xdmp:lock-release|xdmp:lock-acquire|xdmp:load|xdmp:invoke-in|xdmp:invoke|xdmp:integer-to-octal|xdmp:integer-to-hex|xdmp:http-put|xdmp:http-post|xdmp:http-options|xdmp:http-head|xdmp:http-get|xdmp:http-delete|xdmp:hosts|xdmp:host-status|xdmp:host-name|xdmp:host|xdmp:hex-to-integer|xdmp:hash64|xdmp:hash32|xdmp:has-privilege|xdmp:groups|xdmp:group-serves|xdmp:group-servers|xdmp:group-name|xdmp:group-hosts|xdmp:group|xdmp:get-session-field-names|xdmp:get-session-field|xdmp:get-response-encoding|xdmp:get-response-code|xdmp:get-request-username|xdmp:get-request-user|xdmp:get-request-url|xdmp:get-request-protocol|xdmp:get-request-path|xdmp:get-request-method|xdmp:get-request-header-names|xdmp:get-request-header|xdmp:get-request-field-names|xdmp:get-request-field-filename|xdmp:get-request-field-content-type|xdmp:get-request-field|xdmp:get-request-client-certificate|xdmp:get-request-client-address|xdmp:get-request-body|xdmp:get-current-user|xdmp:get-current-roles|xdmp:get|xdmp:function-name|xdmp:function-module|xdmp:function|xdmp:from-json|xdmp:forests|xdmp:forest-status|xdmp:forest-restore|xdmp:forest-restart|xdmp:forest-name|xdmp:forest-delete|xdmp:forest-databases|xdmp:forest-counts|xdmp:forest-clear|xdmp:forest-backup|xdmp:forest|xdmp:filesystem-file|xdmp:filesystem-directory|xdmp:exists|xdmp:excel-convert|xdmp:eval-in|xdmp:eval|xdmp:estimate|xdmp:email|xdmp:element-content-type|xdmp:elapsed-time|xdmp:document-set-quality|xdmp:document-set-property|xdmp:document-set-properties|xdmp:document-set-permissions|xdmp:document-set-collections|xdmp:document-remove-properties|xdmp:document-remove-permissions|xdmp:document-remove-collections|xdmp:document-properties|xdmp:document-locks|xdmp:document-load|xdmp:document-insert|xdmp:document-get-quality|xdmp:document-get-properties|xdmp:document-get-permissions|xdmp:document-get-collections|xdmp:document-get|xdmp:document-forest|xdmp:document-delete|xdmp:document-add-properties|xdmp:document-add-permissions|xdmp:document-add-collections|xdmp:directory-properties|xdmp:directory-locks|xdmp:directory-delete|xdmp:directory-create|xdmp:directory|xdmp:diacritic-less|xdmp:describe|xdmp:default-permissions|xdmp:default-collections|xdmp:databases|xdmp:database-restore-validate|xdmp:database-restore-status|xdmp:database-restore-cancel|xdmp:database-restore|xdmp:database-name|xdmp:database-forests|xdmp:database-backup-validate|xdmp:database-backup-status|xdmp:database-backup-purge|xdmp:database-backup-cancel|xdmp:database-backup|xdmp:database|xdmp:collection-properties|xdmp:collection-locks|xdmp:collection-delete|xdmp:collation-canonical-uri|xdmp:castable-as|xdmp:can-grant-roles|xdmp:base64-encode|xdmp:base64-decode|xdmp:architecture|xdmp:apply|xdmp:amp-roles|xdmp:amp|xdmp:add64|xdmp:add-response-header|xdmp:access|trgr:trigger-set-recursive|trgr:trigger-set-permissions|trgr:trigger-set-name|trgr:trigger-set-module|trgr:trigger-set-event|trgr:trigger-set-description|trgr:trigger-remove-permissions|trgr:trigger-module|trgr:trigger-get-permissions|trgr:trigger-enable|trgr:trigger-disable|trgr:trigger-database-online-event|trgr:trigger-data-event|trgr:trigger-add-permissions|trgr:remove-trigger|trgr:property-content|trgr:pre-commit|trgr:post-commit|trgr:get-trigger-by-id|trgr:get-trigger|trgr:document-scope|trgr:document-content|trgr:directory-scope|trgr:create-trigger|trgr:collection-scope|trgr:any-property-content|thsr:set-entry|thsr:remove-term|thsr:remove-synonym|thsr:remove-entry|thsr:query-lookup|thsr:lookup|thsr:load|thsr:insert|thsr:expand|thsr:add-synonym|spell:suggest-detailed|spell:suggest|spell:remove-word|spell:make-dictionary|spell:load|spell:levenshtein-distance|spell:is-correct|spell:insert|spell:double-metaphone|spell:add-word|sec:users-collection|sec:user-set-roles|sec:user-set-password|sec:user-set-name|sec:user-set-description|sec:user-set-default-permissions|sec:user-set-default-collections|sec:user-remove-roles|sec:user-privileges|sec:user-get-roles|sec:user-get-description|sec:user-get-default-permissions|sec:user-get-default-collections|sec:user-doc-permissions|sec:user-doc-collections|sec:user-add-roles|sec:unprotect-collection|sec:uid-for-name|sec:set-realm|sec:security-version|sec:security-namespace|sec:security-installed|sec:security-collection|sec:roles-collection|sec:role-set-roles|sec:role-set-name|sec:role-set-description|sec:role-set-default-permissions|sec:role-set-default-collections|sec:role-remove-roles|sec:role-privileges|sec:role-get-roles|sec:role-get-description|sec:role-get-default-permissions|sec:role-get-default-collections|sec:role-doc-permissions|sec:role-doc-collections|sec:role-add-roles|sec:remove-user|sec:remove-role-from-users|sec:remove-role-from-role|sec:remove-role-from-privileges|sec:remove-role-from-amps|sec:remove-role|sec:remove-privilege|sec:remove-amp|sec:protect-collection|sec:privileges-collection|sec:privilege-set-roles|sec:privilege-set-name|sec:privilege-remove-roles|sec:privilege-get-roles|sec:privilege-add-roles|sec:priv-doc-permissions|sec:priv-doc-collections|sec:get-user-names|sec:get-unique-elem-id|sec:get-role-names|sec:get-role-ids|sec:get-privilege|sec:get-distinct-permissions|sec:get-collection|sec:get-amp|sec:create-user-with-role|sec:create-user|sec:create-role|sec:create-privilege|sec:create-amp|sec:collections-collection|sec:collection-set-permissions|sec:collection-remove-permissions|sec:collection-get-permissions|sec:collection-add-permissions|sec:check-admin|sec:amps-collection|sec:amp-set-roles|sec:amp-remove-roles|sec:amp-get-roles|sec:amp-doc-permissions|sec:amp-doc-collections|sec:amp-add-roles|search:unparse|search:suggest|search:snippet|search:search|search:resolve-nodes|search:resolve|search:remove-constraint|search:parse|search:get-default-options|search:estimate|search:check-options|prof:value|prof:reset|prof:report|prof:invoke|prof:eval|prof:enable|prof:disable|prof:allowed|ppt:clean|pki:template-set-request|pki:template-set-name|pki:template-set-key-type|pki:template-set-key-options|pki:template-set-description|pki:template-in-use|pki:template-get-version|pki:template-get-request|pki:template-get-name|pki:template-get-key-type|pki:template-get-key-options|pki:template-get-id|pki:template-get-description|pki:need-certificate|pki:is-temporary|pki:insert-trusted-certificates|pki:insert-template|pki:insert-signed-certificates|pki:insert-certificate-revocation-list|pki:get-trusted-certificate-ids|pki:get-template-ids|pki:get-template-certificate-authority|pki:get-template-by-name|pki:get-template|pki:get-pending-certificate-requests-xml|pki:get-pending-certificate-requests-pem|pki:get-pending-certificate-request|pki:get-certificates-for-template-xml|pki:get-certificates-for-template|pki:get-certificates|pki:get-certificate-xml|pki:get-certificate-pem|pki:get-certificate|pki:generate-temporary-certificate-if-necessary|pki:generate-temporary-certificate|pki:generate-template-certificate-authority|pki:generate-certificate-request|pki:delete-template|pki:delete-certificate|pki:create-template|pdf:make-toc|pdf:insert-toc-headers|pdf:get-toc|pdf:clean|p:status-transition|p:state-transition|p:remove|p:pipelines|p:insert|p:get-by-id|p:get|p:execute|p:create|p:condition|p:collection|p:action|ooxml:runs-merge|ooxml:package-uris|ooxml:package-parts-insert|ooxml:package-parts|msword:clean|mcgm:polygon|mcgm:point|mcgm:geospatial-query-from-elements|mcgm:geospatial-query|mcgm:circle|math:tanh|math:tan|math:sqrt|math:sinh|math:sin|math:pow|math:modf|math:log10|math:log|math:ldexp|math:frexp|math:fmod|math:floor|math:fabs|math:exp|math:cosh|math:cos|math:ceil|math:atan2|math:atan|math:asin|math:acos|map:put|map:map|map:keys|map:get|map:delete|map:count|map:clear|lnk:to|lnk:remove|lnk:insert|lnk:get|lnk:from|lnk:create|kml:polygon|kml:point|kml:interior-polygon|kml:geospatial-query-from-elements|kml:geospatial-query|kml:circle|kml:box|gml:polygon|gml:point|gml:interior-polygon|gml:geospatial-query-from-elements|gml:geospatial-query|gml:circle|gml:box|georss:point|georss:geospatial-query|georss:circle|geo:polygon|geo:point|geo:interior-polygon|geo:geospatial-query-from-elements|geo:geospatial-query|geo:circle|geo:box|fn:zero-or-one|fn:years-from-duration|fn:year-from-dateTime|fn:year-from-date|fn:upper-case|fn:unordered|fn:true|fn:translate|fn:trace|fn:tokenize|fn:timezone-from-time|fn:timezone-from-dateTime|fn:timezone-from-date|fn:sum|fn:subtract-dateTimes-yielding-yearMonthDuration|fn:subtract-dateTimes-yielding-dayTimeDuration|fn:substring-before|fn:substring-after|fn:substring|fn:subsequence|fn:string-to-codepoints|fn:string-pad|fn:string-length|fn:string-join|fn:string|fn:static-base-uri|fn:starts-with|fn:seconds-from-time|fn:seconds-from-duration|fn:seconds-from-dateTime|fn:round-half-to-even|fn:round|fn:root|fn:reverse|fn:resolve-uri|fn:resolve-QName|fn:replace|fn:remove|fn:QName|fn:prefix-from-QName|fn:position|fn:one-or-more|fn:number|fn:not|fn:normalize-unicode|fn:normalize-space|fn:node-name|fn:node-kind|fn:nilled|fn:namespace-uri-from-QName|fn:namespace-uri-for-prefix|fn:namespace-uri|fn:name|fn:months-from-duration|fn:month-from-dateTime|fn:month-from-date|fn:minutes-from-time|fn:minutes-from-duration|fn:minutes-from-dateTime|fn:min|fn:max|fn:matches|fn:lower-case|fn:local-name-from-QName|fn:local-name|fn:last|fn:lang|fn:iri-to-uri|fn:insert-before|fn:index-of|fn:in-scope-prefixes|fn:implicit-timezone|fn:idref|fn:id|fn:hours-from-time|fn:hours-from-duration|fn:hours-from-dateTime|fn:floor|fn:false|fn:expanded-QName|fn:exists|fn:exactly-one|fn:escape-uri|fn:escape-html-uri|fn:error|fn:ends-with|fn:encode-for-uri|fn:empty|fn:document-uri|fn:doc-available|fn:doc|fn:distinct-values|fn:distinct-nodes|fn:default-collation|fn:deep-equal|fn:days-from-duration|fn:day-from-dateTime|fn:day-from-date|fn:data|fn:current-time|fn:current-dateTime|fn:current-date|fn:count|fn:contains|fn:concat|fn:compare|fn:collection|fn:codepoints-to-string|fn:codepoint-equal|fn:ceiling|fn:boolean|fn:base-uri|fn:avg|fn:adjust-time-to-timezone|fn:adjust-dateTime-to-timezone|fn:adjust-date-to-timezone|fn:abs|feed:unsubscribe|feed:subscription|feed:subscribe|feed:request|feed:item|feed:description|excel:clean|entity:enrich|dom:set-pipelines|dom:set-permissions|dom:set-name|dom:set-evaluation-context|dom:set-domain-scope|dom:set-description|dom:remove-pipeline|dom:remove-permissions|dom:remove|dom:get|dom:evaluation-context|dom:domains|dom:domain-scope|dom:create|dom:configuration-set-restart-user|dom:configuration-set-permissions|dom:configuration-set-evaluation-context|dom:configuration-set-default-domain|dom:configuration-get|dom:configuration-create|dom:collection|dom:add-pipeline|dom:add-permissions|dls:retention-rules|dls:retention-rule-remove|dls:retention-rule-insert|dls:retention-rule|dls:purge|dls:node-expand|dls:link-references|dls:link-expand|dls:documents-query|dls:document-versions-query|dls:document-version-uri|dls:document-version-query|dls:document-version-delete|dls:document-version-as-of|dls:document-version|dls:document-update|dls:document-unmanage|dls:document-set-quality|dls:document-set-property|dls:document-set-properties|dls:document-set-permissions|dls:document-set-collections|dls:document-retention-rules|dls:document-remove-properties|dls:document-remove-permissions|dls:document-remove-collections|dls:document-purge|dls:document-manage|dls:document-is-managed|dls:document-insert-and-manage|dls:document-include-query|dls:document-history|dls:document-get-permissions|dls:document-extract-part|dls:document-delete|dls:document-checkout-status|dls:document-checkout|dls:document-checkin|dls:document-add-properties|dls:document-add-permissions|dls:document-add-collections|dls:break-checkout|dls:author-query|dls:as-of-query|dbk:convert|dbg:wait|dbg:value|dbg:stopped|dbg:stop|dbg:step|dbg:status|dbg:stack|dbg:out|dbg:next|dbg:line|dbg:invoke|dbg:function|dbg:finish|dbg:expr|dbg:eval|dbg:disconnect|dbg:detach|dbg:continue|dbg:connect|dbg:clear|dbg:breakpoints|dbg:break|dbg:attached|dbg:attach|cvt:save-converted-documents|cvt:part-uri|cvt:destination-uri|cvt:basepath|cvt:basename|cts:words|cts:word-query-weight|cts:word-query-text|cts:word-query-options|cts:word-query|cts:word-match|cts:walk|cts:uris|cts:uri-match|cts:train|cts:tokenize|cts:thresholds|cts:stem|cts:similar-query-weight|cts:similar-query-nodes|cts:similar-query|cts:shortest-distance|cts:search|cts:score|cts:reverse-query-weight|cts:reverse-query-nodes|cts:reverse-query|cts:remainder|cts:registered-query-weight|cts:registered-query-options|cts:registered-query-ids|cts:registered-query|cts:register|cts:query|cts:quality|cts:properties-query-query|cts:properties-query|cts:polygon-vertices|cts:polygon|cts:point-longitude|cts:point-latitude|cts:point|cts:or-query-queries|cts:or-query|cts:not-query-weight|cts:not-query-query|cts:not-query|cts:near-query-weight|cts:near-query-queries|cts:near-query-options|cts:near-query-distance|cts:near-query|cts:highlight|cts:geospatial-co-occurrences|cts:frequency|cts:fitness|cts:field-words|cts:field-word-query-weight|cts:field-word-query-text|cts:field-word-query-options|cts:field-word-query-field-name|cts:field-word-query|cts:field-word-match|cts:entity-highlight|cts:element-words|cts:element-word-query-weight|cts:element-word-query-text|cts:element-word-query-options|cts:element-word-query-element-name|cts:element-word-query|cts:element-word-match|cts:element-values|cts:element-value-ranges|cts:element-value-query-weight|cts:element-value-query-text|cts:element-value-query-options|cts:element-value-query-element-name|cts:element-value-query|cts:element-value-match|cts:element-value-geospatial-co-occurrences|cts:element-value-co-occurrences|cts:element-range-query-weight|cts:element-range-query-value|cts:element-range-query-options|cts:element-range-query-operator|cts:element-range-query-element-name|cts:element-range-query|cts:element-query-query|cts:element-query-element-name|cts:element-query|cts:element-pair-geospatial-values|cts:element-pair-geospatial-value-match|cts:element-pair-geospatial-query-weight|cts:element-pair-geospatial-query-region|cts:element-pair-geospatial-query-options|cts:element-pair-geospatial-query-longitude-name|cts:element-pair-geospatial-query-latitude-name|cts:element-pair-geospatial-query-element-name|cts:element-pair-geospatial-query|cts:element-pair-geospatial-boxes|cts:element-geospatial-values|cts:element-geospatial-value-match|cts:element-geospatial-query-weight|cts:element-geospatial-query-region|cts:element-geospatial-query-options|cts:element-geospatial-query-element-name|cts:element-geospatial-query|cts:element-geospatial-boxes|cts:element-child-geospatial-values|cts:element-child-geospatial-value-match|cts:element-child-geospatial-query-weight|cts:element-child-geospatial-query-region|cts:element-child-geospatial-query-options|cts:element-child-geospatial-query-element-name|cts:element-child-geospatial-query-child-name|cts:element-child-geospatial-query|cts:element-child-geospatial-boxes|cts:element-attribute-words|cts:element-attribute-word-query-weight|cts:element-attribute-word-query-text|cts:element-attribute-word-query-options|cts:element-attribute-word-query-element-name|cts:element-attribute-word-query-attribute-name|cts:element-attribute-word-query|cts:element-attribute-word-match|cts:element-attribute-values|cts:element-attribute-value-ranges|cts:element-attribute-value-query-weight|cts:element-attribute-value-query-text|cts:element-attribute-value-query-options|cts:element-attribute-value-query-element-name|cts:element-attribute-value-query-attribute-name|cts:element-attribute-value-query|cts:element-attribute-value-match|cts:element-attribute-value-geospatial-co-occurrences|cts:element-attribute-value-co-occurrences|cts:element-attribute-range-query-weight|cts:element-attribute-range-query-value|cts:element-attribute-range-query-options|cts:element-attribute-range-query-operator|cts:element-attribute-range-query-element-name|cts:element-attribute-range-query-attribute-name|cts:element-attribute-range-query|cts:element-attribute-pair-geospatial-values|cts:element-attribute-pair-geospatial-value-match|cts:element-attribute-pair-geospatial-query-weight|cts:element-attribute-pair-geospatial-query-region|cts:element-attribute-pair-geospatial-query-options|cts:element-attribute-pair-geospatial-query-longitude-name|cts:element-attribute-pair-geospatial-query-latitude-name|cts:element-attribute-pair-geospatial-query-element-name|cts:element-attribute-pair-geospatial-query|cts:element-attribute-pair-geospatial-boxes|cts:document-query-uris|cts:document-query|cts:distance|cts:directory-query-uris|cts:directory-query-depth|cts:directory-query|cts:destination|cts:deregister|cts:contains|cts:confidence|cts:collections|cts:collection-query-uris|cts:collection-query|cts:collection-match|cts:classify|cts:circle-radius|cts:circle-center|cts:circle|cts:box-west|cts:box-south|cts:box-north|cts:box-east|cts:box|cts:bearing|cts:arc-intersection|cts:and-query-queries|cts:and-query-options|cts:and-query|cts:and-not-query-positive-query|cts:and-not-query-negative-query|cts:and-not-query|css:get|css:convert|cpf:success|cpf:failure|cpf:document-set-state|cpf:document-set-processing-status|cpf:document-set-last-updated|cpf:document-set-error|cpf:document-get-state|cpf:document-get-processing-status|cpf:document-get-last-updated|cpf:document-get-error|cpf:check-transition|alert:spawn-matching-actions|alert:rule-user-id-query|alert:rule-set-user-id|alert:rule-set-query|alert:rule-set-options|alert:rule-set-name|alert:rule-set-description|alert:rule-set-action|alert:rule-remove|alert:rule-name-query|alert:rule-insert|alert:rule-id-query|alert:rule-get-user-id|alert:rule-get-query|alert:rule-get-options|alert:rule-get-name|alert:rule-get-id|alert:rule-get-description|alert:rule-get-action|alert:rule-action-query|alert:remove-triggers|alert:make-rule|alert:make-log-action|alert:make-config|alert:make-action|alert:invoke-matching-actions|alert:get-my-rules|alert:get-all-rules|alert:get-actions|alert:find-matching-rules|alert:create-triggers|alert:config-set-uri|alert:config-set-trigger-ids|alert:config-set-options|alert:config-set-name|alert:config-set-description|alert:config-set-cpf-domain-names|alert:config-set-cpf-domain-ids|alert:config-insert|alert:config-get-uri|alert:config-get-trigger-ids|alert:config-get-options|alert:config-get-name|alert:config-get-id|alert:config-get-description|alert:config-get-cpf-domain-names|alert:config-get-cpf-domain-ids|alert:config-get|alert:config-delete|alert:action-set-options|alert:action-set-name|alert:action-set-module-root|alert:action-set-module-db|alert:action-set-module|alert:action-set-description|alert:action-remove|alert:action-insert|alert:action-get-options|alert:action-get-name|alert:action-get-module-root|alert:action-get-module-db|alert:action-get-module|alert:action-get-description|zero-or-one|years-from-duration|year-from-dateTime|year-from-date|upper-case|unordered|true|translate|trace|tokenize|timezone-from-time|timezone-from-dateTime|timezone-from-date|sum|subtract-dateTimes-yielding-yearMonthDuration|subtract-dateTimes-yielding-dayTimeDuration|substring-before|substring-after|substring|subsequence|string-to-codepoints|string-pad|string-length|string-join|string|static-base-uri|starts-with|seconds-from-time|seconds-from-duration|seconds-from-dateTime|round-half-to-even|round|root|reverse|resolve-uri|resolve-QName|replace|remove|QName|prefix-from-QName|position|one-or-more|number|not|normalize-unicode|normalize-space|node-name|node-kind|nilled|namespace-uri-from-QName|namespace-uri-for-prefix|namespace-uri|name|months-from-duration|month-from-dateTime|month-from-date|minutes-from-time|minutes-from-duration|minutes-from-dateTime|min|max|matches|lower-case|local-name-from-QName|local-name|last|lang|iri-to-uri|insert-before|index-of|in-scope-prefixes|implicit-timezone|idref|id|hours-from-time|hours-from-duration|hours-from-dateTime|floor|false|expanded-QName|exists|exactly-one|escape-uri|escape-html-uri|error|ends-with|encode-for-uri|empty|document-uri|doc-available|doc|distinct-values|distinct-nodes|default-collation|deep-equal|days-from-duration|day-from-dateTime|day-from-date|data|current-time|current-dateTime|current-date|count|contains|concat|compare|collection|codepoints-to-string|codepoint-equal|ceiling|boolean|base-uri|avg|adjust-time-to-timezone|adjust-dateTime-to-timezone|adjust-date-to-timezone|abs)\b/], 3 | ["pln",/^[\w:-]+/],["pln",/^[\t\n\r \xa0]+/]]),["xq","xquery"]); 4 | -------------------------------------------------------------------------------- /jones/static/prettify/lang-yaml.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["pun",/^[:>?|]+/,a,":|>?"],["dec",/^%(?:YAML|TAG)[^\n\r#]+/,a,"%"],["typ",/^&\S+/,a,"&"],["typ",/^!\S*/,a,"!"],["str",/^"(?:[^"\\]|\\.)*(?:"|$)/,a,'"'],["str",/^'(?:[^']|'')*(?:'|$)/,a,"'"],["com",/^#[^\n\r]*/,a,"#"],["pln",/^\s+/,a," \t\r\n"]],[["dec",/^(?:---|\.\.\.)(?:[\n\r]|$)/],["pun",/^-/],["kwd",/^\w+:[\n\r ]/],["pln",/^\w+/]]),["yaml","yml"]); 3 | -------------------------------------------------------------------------------- /jones/static/prettify/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} -------------------------------------------------------------------------------- /jones/static/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p 19 | 22 |
23 |
24 |
    25 | {% for svc in services %} 26 |
  • {{ svc }}
  • 27 | {% endfor %} 28 |
29 |
30 |
31 | 32 | 33 | {% endblock %} 34 | -------------------------------------------------------------------------------- /jones/templates/layout.j2: -------------------------------------------------------------------------------- 1 | {# 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | #} 14 | 15 | 16 | 17 | 18 | 19 | Jones 20 | 21 | 22 | 23 | 24 | 25 | {% block head %}{% endblock %} 26 | 27 | 28 | 29 | 30 | 69 | 70 |
71 | 72 | {% with messages = get_flashed_messages() %} 73 | {% if messages %} 74 |
75 | {% for message in messages %} 76 |
77 | 78 | {{ message }} 79 |
80 | {% endfor %} 81 |
82 | {% endif %} 83 | {% endwith %} 84 | 85 | {% block body %}{% endblock %} 86 |
87 | 88 | 89 | 90 | 91 | 123 | {% block footer %}{% endblock %} 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /jones/templates/service.j2: -------------------------------------------------------------------------------- 1 | {# 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | #} 14 | 15 | {% set active_page = service -%} 16 | {% extends "layout.j2" %} 17 | {% block head %} 18 | 19 | 20 | 23 | {% endblock %} 24 | {% block body %} 25 | 47 | 48 | 49 |
50 | 51 | 56 | 57 |
58 |
59 |

Environments

60 | 61 | 62 | {% for child in children %} 63 | {% set issame = (child["env"] == env )%} 64 | {#% set child_url = "/service/%s/%s/"|format(service, child["env"]) %#} 65 | {% set child_url = "/service/%s/"|format(service) %} 66 | {% if not child["env"].is_root %} 67 | {% set child_url = "%s%s/"|format(child_url, child["env"]) %} 68 | {% endif %} 69 | 70 | {% if not child["is_leaf"] %} 71 | 82 | {% if child["is_leaf"] %} 83 | 86 | {% endif %} 87 | 90 | 91 | {% endfor %} 92 | 93 |
72 | {% else %} 73 | 74 | {% endif %} 75 | {% if issame%} 76 | /{{ child["env"] }} 77 | {% else %} 78 | 79 | /{{ child["env"] }} 80 | {% endif %} 81 | 84 | Delete 85 | 88 | Add Child 89 |
94 | 95 |

Associations

96 | {% if env.is_root %} 97 |
98 | All associations default to root. 99 |
100 | {% else %} 101 |
102 |
    103 | {% for hostname in associations %} 104 |
  • 105 | {# TODO: only eval this loop if assocs[hostname] == this environment. #} 106 | {{ hostname }} 107 | 108 | 110 | 111 |
  • 112 | {% else %} 113 |
  • None
  • 114 | {% endfor %} 115 |
116 | 128 | 129 |
130 | {% endif %} 131 | 132 |

Inherited View

133 |
{{ view | as_json(2) }}
134 |
135 | 136 |
137 |

138 |

139 |
140 |

141 |

142 | 143 |

144 |
145 |
146 |
147 | {% endblock %} 148 | {% block footer %} 149 | 150 | 156 | 157 | 158 | 159 | {% endblock %} 160 | -------------------------------------------------------------------------------- /jones/web.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | 15 | 16 | from flask import Flask, jsonify, redirect, render_template, request, url_for 17 | from itertools import repeat, izip, imap 18 | from jinja2 import Markup 19 | from kazoo.client import KazooClient 20 | from kazoo.exceptions import NoNodeException 21 | from kazoo.security import make_acl, make_digest_acl_credential 22 | from raven.contrib.flask import Sentry 23 | from werkzeug.contrib.fixers import ProxyFix 24 | import json 25 | 26 | from jones import Jones, Env 27 | import zkutil 28 | import jonesconfig 29 | 30 | 31 | app = Flask(__name__) 32 | app.wsgi_app = ProxyFix(app.wsgi_app) 33 | app.config.from_object(jonesconfig) 34 | app.config.from_envvar('JONES_SETTINGS', silent=True) 35 | 36 | if 'SENTRY_DSN' in app.config: 37 | sentry = Sentry(app) 38 | 39 | jones_credential = make_digest_acl_credential( 40 | 'Jones', app.config['ZK_DIGEST_PASSWORD'] 41 | ) 42 | 43 | _zk = None 44 | 45 | 46 | def get_zk(): 47 | global _zk 48 | if _zk is None: 49 | _zk = KazooClient( 50 | app.config['ZK_CONNECTION_STRING'], 51 | default_acl=( 52 | # grants read permissions to anyone. 53 | make_acl('world', 'anyone', read=True), 54 | # grants all permissions to the creator of the node. 55 | make_acl('auth', '', all=True) 56 | ) 57 | ) 58 | _zk.start() 59 | _zk.add_auth('digest', jones_credential) 60 | _zk.DataWatch('/services', func=ensure_root) 61 | return _zk 62 | 63 | 64 | def ensure_root(data, stat): 65 | if not data: 66 | get_zk().ensure_path('/services') 67 | 68 | 69 | def request_wants(t): 70 | types = ['text/plain', 'text/html', 'application/json'] 71 | assert t in types 72 | best = request.accept_mimetypes \ 73 | .best_match(types) 74 | return best == t 75 | 76 | 77 | @app.template_filter() 78 | def as_json(d, indent=None): 79 | return Markup(json.dumps(d, indent=indent)) 80 | 81 | 82 | @app.context_processor 83 | def inject_services(): 84 | return dict(services=[child for child in get_zk().get_children('/services') if 85 | Jones(child, get_zk()).exists()]) 86 | 87 | 88 | @app.route('/') 89 | def index(): 90 | return render_template('index.j2') 91 | 92 | 93 | def service_create(env, jones): 94 | 95 | jones.create_config(env, {}) 96 | if request_wants('application/json') or request_wants('text/plain'): 97 | r = jsonify(service=jones.service) 98 | r.status_code = 201 99 | return r 100 | else: 101 | if env.is_root: 102 | env = None 103 | 104 | return redirect(url_for( 105 | 'services', service=jones.service, env=env)) 106 | 107 | 108 | def service_update(env, jones): 109 | jones.set_config( 110 | env, 111 | json.loads(request.form['data']), 112 | int(request.form['version']) 113 | ) 114 | return env 115 | 116 | 117 | def service_delete(env, jones): 118 | if env.is_root: 119 | # deleting whole service 120 | jones.delete_all() 121 | #return redirect(url_for('index')) 122 | else: 123 | jones.delete_config(env, -1) 124 | return env, 200 125 | 126 | 127 | def service_get(env, jones): 128 | if not jones.exists(): 129 | return redirect(url_for('index')) 130 | 131 | children = jones.get_child_envs(Env.Root) 132 | is_leaf = lambda child: len(child) and not any( 133 | c.find(child + '/') >= 0 for c in children) 134 | 135 | try: 136 | version, config = jones.get_config_by_env(env) 137 | except NoNodeException: 138 | return redirect(url_for('services', service=jones.service)) 139 | 140 | childs = imap(dict, izip( 141 | izip(repeat('env'), imap(Env, children)), 142 | izip(repeat('is_leaf'), imap(is_leaf, children)))) 143 | 144 | vals = { 145 | "env": env, 146 | "version": version, 147 | "children": list(childs), 148 | "config": config, 149 | "view": jones.get_view_by_env(env), 150 | "service": jones.service, 151 | "associations": jones.get_associations(env) 152 | } 153 | if request_wants('application/json'): 154 | return jsonify(vals) 155 | else: 156 | return render_template('service.j2', **vals) 157 | 158 | 159 | SERVICE = { 160 | 'get': service_get, 161 | 'put': service_update, 162 | 'post': service_create, 163 | 'delete': service_delete 164 | } 165 | 166 | ALL_METHODS = ['GET', 'PUT', 'POST', 'DELETE'] 167 | 168 | 169 | @app.route('/service//', defaults={'env': None}, 170 | methods=ALL_METHODS) 171 | @app.route('/service///', methods=ALL_METHODS) 172 | def services(service, env): 173 | jones = Jones(service, get_zk()) 174 | environment = Env(env) 175 | 176 | return SERVICE[request.method.lower()](environment, jones) 177 | 178 | 179 | @app.route('/service//association/', 180 | methods=['GET', 'PUT', 'DELETE']) 181 | def association(service, assoc): 182 | jones = Jones(service, get_zk()) 183 | 184 | if request.method == 'GET': 185 | if request_wants('application/json'): 186 | return jsonify(jones.get_config(assoc)) 187 | if request.method == 'PUT': 188 | jones.assoc_host(assoc, Env(request.form['env'])) 189 | return service, 201 190 | elif request.method == 'DELETE': 191 | jones.delete_association(assoc) 192 | return service, 200 193 | 194 | 195 | @app.route('/export') 196 | def export(): 197 | return zkutil.export_tree(get_zk(), '/') 198 | 199 | 200 | if __name__ == '__main__': 201 | app.run() 202 | -------------------------------------------------------------------------------- /jones/zkutil.py: -------------------------------------------------------------------------------- 1 | def export_tree(zk, root): 2 | out = [] 3 | for path in walk(zk, root): 4 | spacing = ' ' * 2 * (path.count('/') - 1) 5 | long_spacing = spacing + (' ' * 2) 6 | out.append(spacing + path) 7 | data, stat = zk.get(path) 8 | if len(data): 9 | out.append( 10 | ''.join(long_spacing + line for line in data.splitlines(True)) 11 | ) 12 | return '\n'.join(out) 13 | 14 | 15 | def walk(zk, path='/'): 16 | """Yields all paths under `path`.""" 17 | children = zk.get_children(path) 18 | yield path 19 | for child in children: 20 | if path == '/': 21 | subpath = "/%s" % child 22 | else: 23 | subpath = "%s/%s" % (path, child) 24 | 25 | for child in walk(zk, subpath): 26 | yield child 27 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2012 DISQUS 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | from setuptools import setup 18 | 19 | # version only changes when client changes. 20 | VERSION = '1.0.0' 21 | NAME = 'jones' 22 | 23 | install_requires = [ 24 | 'kazoo==1.2.1' 25 | ] 26 | 27 | web_requires = install_requires + [ 28 | 'flask', 29 | 'raven' 30 | ] 31 | 32 | tests_require = web_requires + [ 33 | 'nose', 34 | 'unittest2', 35 | 'mock', 36 | 'gevent' 37 | ] 38 | 39 | if __name__ == '__main__': 40 | setup( 41 | name=NAME, 42 | version=VERSION, 43 | author='Matthew Hooker', 44 | author_email='mwhooker@gmail.com', 45 | url='https://github.com/mwhooker/jones', 46 | description='Configuration frontend for Zookeeper.', 47 | license='Apache License 2.0', 48 | py_modules=['jones.client'], 49 | zip_safe=False, 50 | install_requires=install_requires, 51 | tests_require=tests_require, 52 | extras_require={ 53 | 'test': tests_require, 54 | 'web': web_requires 55 | }, 56 | test_suite='nose.collector', 57 | include_package_data=True, 58 | ) 59 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | -------------------------------------------------------------------------------- /tests/fixture.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | from __future__ import unicode_literals 15 | from jones.jones import Env 16 | 17 | 18 | # TODO: rewrite tests so we test everything 19 | CONFIG = { 20 | 'parent': { 21 | 'a': 1, 22 | 'b': [1, 2, 3], 23 | 'c': {'x': 0} 24 | }, 25 | 'child1': { 26 | 'a': 2 27 | }, 28 | 'child2': { 29 | 'a': 3 30 | }, 31 | 'subchild1': { 32 | 'b': "abc" 33 | }, 34 | 'root': { 35 | 'foo': 'bar' 36 | } 37 | } 38 | 39 | CHILD1 = {} 40 | for k in ('root', 'parent', 'child1'): 41 | CHILD1.update(**CONFIG[k]) 42 | PARENT = {} 43 | 44 | for k in ('root', 'parent'): 45 | PARENT.update(**CONFIG[k]) 46 | 47 | 48 | ASSOCIATIONS = { 49 | '127.0.0.3': Env(''), 50 | '127.0.0.1': Env('parent'), 51 | '127.0.0.2': Env('parent/child1') 52 | } 53 | 54 | nodes = [Env(i) for i in [ 55 | None, 'parent', 'parent/child1', 'parent/child2', 56 | 'parent/child2/subchild1']] 57 | 58 | values = [CONFIG['root'], 59 | CONFIG['parent'], 60 | CONFIG['child1'], 61 | CONFIG['child2'], 62 | CONFIG['subchild1']] 63 | 64 | 65 | def init_tree(jones): 66 | 67 | for node, value in zip(nodes, values): 68 | jones.create_config(node, value) 69 | for host in ASSOCIATIONS: 70 | jones.assoc_host(host, ASSOCIATIONS[host]) 71 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | 15 | from __future__ import unicode_literals 16 | import threading 17 | 18 | from tests import fixture 19 | from jones.jones import Jones, Env 20 | from jones.client import JonesClient, EnvironmentNotFoundException 21 | from kazoo.testing import KazooTestCase 22 | 23 | 24 | """ 25 | Because Kazoo's testing framework actually shells out to the local zookeeper 26 | install, we've introduced real world uncertainty to our tests. A side-effect 27 | of which we are attempting to work around by introduce wall-clock delays. 28 | These are designed to allow time for zookeeper to invoke our callbacks before 29 | testing for expected results. 30 | 31 | The amount of wall-clock delay is reflected in the below magic number. 32 | """ 33 | 34 | MAGIC_NUMBER = 0.5 35 | 36 | 37 | class TestJonesClient(KazooTestCase): 38 | 39 | def setUp(self): 40 | super(TestJonesClient, self).setUp() 41 | self.config = None 42 | self.service = 'testservice' 43 | self.hostname = '127.0.0.2' 44 | self.ev = threading.Event() 45 | 46 | self.jones = Jones(self.service, self.client) 47 | fixture.init_tree(self.jones) 48 | self.jones_client = JonesClient(self.client, self.service, 49 | self.default_cb, self.hostname) 50 | 51 | def default_cb(self, config): 52 | self.config = config 53 | self.ev.set() 54 | 55 | def test_gets_config(self): 56 | self.ev.wait(MAGIC_NUMBER) 57 | self.assertEquals(self.jones_client, fixture.CHILD1) 58 | self.assertEquals(self.config, fixture.CHILD1) 59 | 60 | def test_default_value(self): 61 | self.ev.wait(MAGIC_NUMBER) 62 | self.assertEquals(self.jones_client.get('a'), fixture.CHILD1['a']) 63 | self.assertEquals(self.jones_client.get('notinhere', 1), 1) 64 | 65 | def test_raises_with_no_environment(self): 66 | self.assertRaises( 67 | EnvironmentNotFoundException, 68 | JonesClient, 69 | self.client, 'newservice' 70 | ) 71 | 72 | def test_responds_to_remap(self): 73 | """test that changing the associations updates config properly.""" 74 | 75 | self.ev.clear() 76 | self.jones.assoc_host(self.hostname, Env('parent')) 77 | self.ev.wait(MAGIC_NUMBER) 78 | self.assertEquals(self.config, fixture.PARENT) 79 | 80 | def test_defaults_to_root(self): 81 | """ 82 | If a hostname doesn't map to anything, 83 | make sure to default to the root. 84 | That way we don't have to add every 85 | host under our control to zk. 86 | """ 87 | 88 | ev = threading.Event() 89 | hostname = '0.0.0.0' 90 | self.assertTrue(hostname not in fixture.ASSOCIATIONS.keys()) 91 | 92 | def cb(config): 93 | ev.set() 94 | 95 | client = JonesClient(self.client, self.service, cb, hostname) 96 | ev.wait(MAGIC_NUMBER) 97 | self.assertEquals(client.config, fixture.CONFIG['root']) 98 | self.assertTrue(ev.isSet()) 99 | 100 | ev.clear() 101 | self.jones.assoc_host(hostname, Env('parent')) 102 | ev.wait(MAGIC_NUMBER) 103 | self.assertEquals(client.config, fixture.PARENT) 104 | self.assertTrue(ev.isSet()) 105 | 106 | def test_works_if_zk_down(self): 107 | self.assertEquals(self.config, fixture.CHILD1) 108 | self.expire_session() 109 | self.assertEquals(self.config, fixture.CHILD1) 110 | 111 | def test_resets_watches(self): 112 | def test(fixt): 113 | self.ev.clear() 114 | self.jones.set_config(Env('parent'), {'k': fixt}, -1) 115 | self.ev.wait(MAGIC_NUMBER) 116 | self.assertEquals(self.config['k'], fixt) 117 | 118 | for fixt in ('a', 'b', 'c'): 119 | test(fixt) 120 | -------------------------------------------------------------------------------- /tests/test_jones.py: -------------------------------------------------------------------------------- 1 | """ 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | """ 14 | 15 | from __future__ import unicode_literals 16 | import json 17 | 18 | from kazoo.exceptions import BadVersionException, NoNodeException 19 | from kazoo.testing.harness import KazooTestCase 20 | from tests import fixture 21 | 22 | from jones.jones import Jones, Env 23 | 24 | 25 | class TestJones(KazooTestCase): 26 | 27 | def setUp(self): 28 | super(TestJones, self).setUp() 29 | 30 | self.jones = Jones('testservice', self.client) 31 | 32 | def test_creates_root(self): 33 | 34 | fixt = {'xy': 'z'} 35 | self.jones.create_config(Env.Root, fixt) 36 | print json.loads(self.client.get(self.jones.view_path)[0]) 37 | self.assertEquals( 38 | json.loads(self.client.get(self.jones.view_path)[0]), 39 | fixt 40 | ) 41 | 42 | def test_jones(self): 43 | 44 | fixture.init_tree(self.jones) 45 | self.assertEquals( 46 | fixture.CHILD1, 47 | self.jones.get_config('127.0.0.2') 48 | ) 49 | 50 | def test_overwrites(self): 51 | self.jones.create_config(Env.Root, {"foo": "bar"}) 52 | self.jones.set_config(Env.Root, {"foo": "baz"}, -1) 53 | 54 | self.assertEquals( 55 | self.jones._get(self.jones.conf_path)[1]['foo'], 56 | 'baz' 57 | ) 58 | 59 | def test_parent_changed(self): 60 | fixture.init_tree(self.jones) 61 | parent = dict(fixture.CONFIG['parent']) 62 | parent['new'] = 'key' 63 | self.jones.set_config(Env('parent'), parent, 0) 64 | #self.client.print_tree('/services') 65 | 66 | for i in ('127.0.0.1', '127.0.0.2'): 67 | config = self.jones.get_config(i) 68 | self.assertEquals(config.get('b'), [1, 2, 3], 69 | "Host %s didn't inherit properly." % i) 70 | self.assertEquals(config.get('new'), 'key', 71 | "Host %s not updated." % i) 72 | 73 | def test_conflicts(self): 74 | 75 | self.jones.create_config(Env.Root, {"foo": "bar"}) 76 | self.jones.set_config(Env.Root, {"foo": "baz"}, 0) 77 | 78 | self.assertEquals( 79 | self.jones._get(self.jones.conf_path)[1]['foo'], 80 | 'baz' 81 | ) 82 | 83 | self.assertRaises( 84 | BadVersionException, 85 | self.jones.set_config, 86 | Env.Root, {"foo": "bag"}, 4, 87 | ) 88 | 89 | def test_delete_config(self): 90 | fixture.init_tree(self.jones) 91 | env = Env('parent/child1') 92 | self.jones.delete_config(env, -1) 93 | 94 | self.assertRaises( 95 | NoNodeException, 96 | self.jones.get_config, 97 | '127.0.0.2' 98 | ) 99 | 100 | self.assertRaises( 101 | NoNodeException, 102 | self.jones.get_config_by_env, 103 | env 104 | ) 105 | 106 | self.assertRaises( 107 | NoNodeException, 108 | self.jones.get_view_by_env, 109 | env 110 | ) 111 | 112 | def test_conf_is_mapping(self): 113 | """Make sure create_config only allows collections.Mapping types""" 114 | 115 | self.assertRaises( 116 | ValueError, 117 | self.jones.create_config, 118 | Env.Root, 'hello' 119 | ) 120 | 121 | def test_get_associations(self): 122 | fixture.init_tree(self.jones) 123 | assocs = self.jones.associations.get_all() 124 | for host in fixture.ASSOCIATIONS: 125 | self.assertEquals( 126 | assocs[host], 127 | self.jones._get_view_path(fixture.ASSOCIATIONS[host]) 128 | ) 129 | 130 | def test_delete_association(self): 131 | fixture.init_tree(self.jones) 132 | self.jones.delete_association('127.0.0.3') 133 | self.assertRaises( 134 | KeyError, 135 | self.jones.get_config, 136 | '127.0.0.3' 137 | ) 138 | self.assertTrue(len(self.jones.associations.get_all()) > 0) 139 | 140 | def test_create_service(self): 141 | """Test that creating a service creates stub conf/view/nodemaps.""" 142 | 143 | env = Env.Root 144 | self.jones.create_config(env, {}) 145 | self.assertEquals(self.jones.get_associations(env), None) 146 | self.assertEquals(self.jones.get_view_by_env(env), {}) 147 | self.assertEquals(self.jones.get_config_by_env(env)[1], {}) 148 | self.assertEquals(self.jones.get_child_envs(env), ['']) 149 | 150 | def test_exists_reflectes_creation(self): 151 | self.assertFalse(self.jones.exists()) 152 | self.jones.create_config(Env.Root, {}) 153 | self.assertTrue(self.jones.exists()) 154 | 155 | def test_delete_service(self): 156 | """Test that deleting a service removes all sub-nodes.""" 157 | 158 | env = Env.Root 159 | self.jones.create_config(env, {}) 160 | 161 | self.jones.delete_all() 162 | self.assertRaises( 163 | NoNodeException, 164 | self.client.get, 165 | self.jones.root 166 | ) 167 | -------------------------------------------------------------------------------- /tests/test_zkutil.py: -------------------------------------------------------------------------------- 1 | from jones import zkutil 2 | from kazoo.testing import KazooTestCase 3 | 4 | 5 | class TestZKUtil(KazooTestCase): 6 | 7 | def test_walk(self): 8 | expected = ['/test', '/test/a', '/test/a/b', '/test/z'] 9 | for i in expected: 10 | self.client.create(i, '') 11 | 12 | self.assertEquals( 13 | sorted(zkutil.walk(self.client, '/test')), 14 | sorted(expected) 15 | ) 16 | 17 | -------------------------------------------------------------------------------- /tests/test_znode_map.py: -------------------------------------------------------------------------------- 1 | from kazoo.testing import KazooTestCase 2 | 3 | from jones.jones import ZNodeMap 4 | 5 | 6 | class TestZNodeMap(KazooTestCase): 7 | 8 | def setUp(self): 9 | super(TestZNodeMap, self).setUp() 10 | self.links = ZNodeMap(self.client, "/nodemaps") 11 | 12 | def test_set(self): 13 | self.links.set('src', 'dest') 14 | self.assertEquals(self.links.get('src'), 'dest') 15 | --------------------------------------------------------------------------------