diff --git a/ReadME.MD b/ReadME.MD index dd2686e..d66a0fe 100644 --- a/ReadME.MD +++ b/ReadME.MD @@ -9,11 +9,9 @@ charm pull cs:~containers/kubernetes-master-990 charm pull cs:~containers/kubernetes-worker-757 charm pull cs:~containers/calico-812 charm pull cs:~containers/containerd-119 -charm pull cs:~containers/kata-108 # Extend charm pull cs:~containers/kubeapi-load-balancer-786 charm pull cs:~containers/keepalived-85 -charm pull cs:~containers/coredns-20 ``` ## These are the container images used by this release: @@ -61,4 +59,4 @@ sig-storage/csi-resizer:v0.5.1 sig-storage/csi-snapshotter:v2.1.3 sig-storage/livenessprobe:v2.1.0 sonatype/nexus3:latest -``` \ No newline at end of file +``` diff --git a/coredns/.github/workflows/tests.yaml b/coredns/.github/workflows/tests.yaml deleted file mode 100644 index 9b2b55e..0000000 --- a/coredns/.github/workflows/tests.yaml +++ /dev/null @@ -1,92 +0,0 @@ -name: Test Suite for CoreDNS - -on: - - pull_request - -jobs: - lint-and-unit-tests: - name: Lint & Unit tests - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v2 - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Install Tox - run: pip install tox - - name: Run lint & unit tests - run: tox - - func-test: - name: Functional test with MicroK8s - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Check out code - uses: actions/checkout@v2 - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - name: Fix global gitconfig for confined snap - run: | - # GH automatically includes the git-lfs plugin and configures it in - # /etc/gitconfig. However, the confinement of the charmcraft snap - # means that it can see that this file exists but cannot read it, even - # if the file permissions should allow it; this breaks git usage within - # the snap. To get around this, we move it from the global gitconfig to - # the user's .gitconfig file. - cat /etc/gitconfig >> $HOME/.gitconfig - sudo rm /etc/gitconfig - - name: Install MicroK8s - uses: balchua/microk8s-actions@v0.1.3 - with: - rbac: 'true' - storage: 'true' - dns: 'true' # required for juju, will adjust later - - name: Install Dependencies - run: | - pip install tox - sudo snap install juju --classic - sudo snap install juju-wait --classic - sudo usermod -aG microk8s $USER - sudo snap install charmcraft --beta - sudo snap install yq - - name: Build charm - run: | - if ! charmcraft build; then - echo Build failed, full log: - cat "$(ls -1t "$HOME"/snap/charmcraft/common/charmcraft-log-* | head -n1)" - exit 1 - fi - - name: Bootstrap MicroK8s with Juju - run: sg microk8s 'juju bootstrap microk8s microk8s' - - name: Add model - run: juju add-model coredns microk8s - - name: Deploy CoreDNS - run: | - upstream_image=$(yq eval '.resources.coredns-image.upstream-source' metadata.yaml) - juju deploy ./coredns.charm --resource coredns-image=$upstream_image --config forward=8.8.8.8 - - name: Wait for stable environment - run: juju wait -wv - - name: Tell MicroK8s to use CoreDNS charm - run: | - cluster_ip=$(sudo microk8s.kubectl get svc -n coredns coredns -o jsonpath='{..spec.clusterIP}') - sudo sed -i -e "s/--cluster-dns=.*/--cluster-dns=$cluster_ip/" /var/snap/microk8s/current/args/kubelet - sudo systemctl restart snap.microk8s.daemon-kubelet - - name: Run functional test - run: tox -e func - - name: Juju Status - if: failure() - run: sudo juju status - - name: Juju Log - if: failure() - run: sudo juju debug-log --replay --no-tail -i coredns - - name: Microk8s Status - if: failure() - run: sudo microk8s.kubectl get all -A - - name: Microk8s Pod Log - if: failure() - run: sudo microk8s.kubectl logs -n coredns -l juju-app=coredns diff --git a/coredns/.gitignore b/coredns/.gitignore deleted file mode 100644 index 878f4f1..0000000 --- a/coredns/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -.tox/ -__pycache__/ -*.pyc -placeholders/ -*.charm -build/ diff --git a/coredns/CONTRIBUTING.md b/coredns/CONTRIBUTING.md deleted file mode 100644 index e8f19f1..0000000 --- a/coredns/CONTRIBUTING.md +++ /dev/null @@ -1,34 +0,0 @@ -# Contributor Guide - -This Juju charm is open source ([Apache License 2.0](./LICENSE)) and we actively seek any community contibutions -for code, suggestions and documentation. -This page details a few notes, workflows and suggestions for how to make contributions most effective and help us -all build a better charm - please give them a read before working on any contributions. - -## Licensing - -This charm has been created under the [Apache License 2.0](./LICENSE), which will cover any contributions you may -make to this project. Please familiarise yourself with the terms of the license. - -Additionally, this charm uses the Harmony CLA agreement. It’s the easiest way for you to give us permission to -use your contributions. -In effect, you’re giving us a license, but you still own the copyright — so you retain the right to modify your -code and use it in other projects. Please [sign the CLA here](https://ubuntu.com/legal/contributors/agreement) before -making any contributions. - -## Code of conduct - -We have adopted the Ubuntu code of Conduct. You can read this in full [here](https://ubuntu.com/community/code-of-conduct). - -## Contributing code - -To contribute code to this project, please use the following workflow: - -1. [Submit a bug](https://bugs.launchpad.net/charm-coredns/+filebug) to explain the need for and track the change. -2. Create a branch on your fork of the repo with your changes, including a unit test covering the new or modified code. -3. Submit a PR. The PR description should include a link to the bug on Launchpad. -4. Update the Launchpad bug to include a link to the PR and the `review-needed` tag. -5. Once reviewed and merged, the change will become available on the edge channel and assigned to an appropriate milestone - for further release according to priority. - - diff --git a/coredns/LICENSE b/coredns/LICENSE deleted file mode 100644 index 7a4a3ea..0000000 --- a/coredns/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/coredns/Pipfile b/coredns/Pipfile deleted file mode 100644 index 466f522..0000000 --- a/coredns/Pipfile +++ /dev/null @@ -1,16 +0,0 @@ -[[source]] -name = "pypi" -url = "https://pypi.org/simple" -verify_ssl = true - -[dev-packages] -pytest = "*" -flake8 = "*" -ipdb = "*" - -[packages] -ops = "*" -oci-image = {git = "https://github.com/juju-solutions/resource-oci-image/"} - -[requires] -python_version = "3.8" diff --git a/coredns/Pipfile.lock b/coredns/Pipfile.lock deleted file mode 100644 index f6cd306..0000000 --- a/coredns/Pipfile.lock +++ /dev/null @@ -1,246 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "3a93ef1bf6ad71dacc9efebae3e194bb569d6bf8728161b19e95dbd7c407aa22" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.8" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "oci-image": { - "git": "https://github.com/juju-solutions/resource-oci-image/", - "ref": "c5778285d332edf3d9a538f9d0c06154b7ec1b0b" - }, - "ops": { - "hashes": [ - "sha256:23556db47b2c97a1bb72845b7c8ec88aa7a3e27717402903b5fea7b659616ab8", - "sha256:d102359496584617a00f6f42525a01d1b60269a3d41788cf025738cbe3348c99" - ], - "index": "pypi", - "version": "==0.10.0" - }, - "pyyaml": { - "hashes": [ - "sha256:02c78d77281d8f8d07a255e57abdbf43b02257f59f50cc6b636937d68efa5dd0", - "sha256:0dc9f2eb2e3c97640928dec63fd8dc1dd91e6b6ed236bd5ac00332b99b5c2ff9", - "sha256:124fd7c7bc1e95b1eafc60825f2daf67c73ce7b33f1194731240d24b0d1bf628", - "sha256:26fcb33776857f4072601502d93e1a619f166c9c00befb52826e7b774efaa9db", - "sha256:31ba07c54ef4a897758563e3a0fcc60077698df10180abe4b8165d9895c00ebf", - "sha256:3c49e39ac034fd64fd576d63bb4db53cda89b362768a67f07749d55f128ac18a", - "sha256:52bf0930903818e600ae6c2901f748bc4869c0c406056f679ab9614e5d21a166", - "sha256:5a3f345acff76cad4aa9cb171ee76c590f37394186325d53d1aa25318b0d4a09", - "sha256:5e7ac4e0e79a53451dc2814f6876c2fa6f71452de1498bbe29c0b54b69a986f4", - "sha256:7242790ab6c20316b8e7bb545be48d7ed36e26bbe279fd56f2c4a12510e60b4b", - "sha256:737bd70e454a284d456aa1fa71a0b429dd527bcbf52c5c33f7c8eee81ac16b89", - "sha256:8635d53223b1f561b081ff4adecb828fd484b8efffe542edcfdff471997f7c39", - "sha256:8b818b6c5a920cbe4203b5a6b14256f0e5244338244560da89b7b0f1313ea4b6", - "sha256:8bf38641b4713d77da19e91f8b5296b832e4db87338d6aeffe422d42f1ca896d", - "sha256:a36a48a51e5471513a5aea920cdad84cbd56d70a5057cca3499a637496ea379c", - "sha256:b2243dd033fd02c01212ad5c601dafb44fbb293065f430b0d3dbf03f3254d615", - "sha256:cc547d3ead3754712223abb7b403f0a184e4c3eae18c9bb7fd15adef1597cc4b", - "sha256:cc552b6434b90d9dbed6a4f13339625dc466fd82597119897e9489c953acbc22", - "sha256:f3790156c606299ff499ec44db422f66f05a7363b39eb9d5b064f17bd7d7c47b", - "sha256:f7a21e3d99aa3095ef0553e7ceba36fb693998fbb1226f1392ce33681047465f", - "sha256:fdc6b2cb4b19e431994f25a9160695cc59a4e861710cc6fc97161c5e845fc579" - ], - "index": "pypi", - "version": "==5.4" - } - }, - "develop": { - "attrs": { - "hashes": [ - "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", - "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" - ], - "version": "==20.3.0" - }, - "backcall": { - "hashes": [ - "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", - "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255" - ], - "version": "==0.2.0" - }, - "decorator": { - "hashes": [ - "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", - "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" - ], - "version": "==4.4.2" - }, - "flake8": { - "hashes": [ - "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c", - "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208" - ], - "index": "pypi", - "version": "==3.8.3" - }, - "iniconfig": { - "hashes": [ - "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", - "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" - ], - "version": "==1.1.1" - }, - "ipdb": { - "hashes": [ - "sha256:d6f46d261c45a65e65a2f7ec69288a1c511e16206edb2875e7ec6b2f66997e78" - ], - "index": "pypi", - "version": "==0.13.3" - }, - "ipython": { - "hashes": [ - "sha256:04323f72d5b85b606330b6d7e2dc8d2683ad46c3905e955aa96ecc7a99388e70", - "sha256:34207ffb2f653bced2bc8e3756c1db86e7d93e44ed049daae9814fed66d408ec" - ], - "version": "==7.21.0" - }, - "ipython-genutils": { - "hashes": [ - "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", - "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" - ], - "version": "==0.2.0" - }, - "jedi": { - "hashes": [ - "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93", - "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707" - ], - "version": "==0.18.0" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "packaging": { - "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" - ], - "version": "==20.9" - }, - "parso": { - "hashes": [ - "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410", - "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e" - ], - "version": "==0.8.1" - }, - "pexpect": { - "hashes": [ - "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", - "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" - ], - "markers": "sys_platform != 'win32'", - "version": "==4.8.0" - }, - "pickleshare": { - "hashes": [ - "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", - "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" - ], - "version": "==0.7.5" - }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "version": "==0.13.1" - }, - "prompt-toolkit": { - "hashes": [ - "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04", - "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc" - ], - "version": "==3.0.18" - }, - "ptyprocess": { - "hashes": [ - "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", - "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" - ], - "version": "==0.7.0" - }, - "py": { - "hashes": [ - "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", - "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" - ], - "version": "==1.10.0" - }, - "pycodestyle": { - "hashes": [ - "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", - "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" - ], - "version": "==2.6.0" - }, - "pyflakes": { - "hashes": [ - "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", - "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" - ], - "version": "==2.2.0" - }, - "pygments": { - "hashes": [ - "sha256:2656e1a6edcdabf4275f9a3640db59fd5de107d88e8663c5d4e9a0fa62f77f94", - "sha256:534ef71d539ae97d4c3a4cf7d6f110f214b0e687e92f9cb9d2a3b0d3101289c8" - ], - "version": "==2.8.1" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" - ], - "version": "==2.4.7" - }, - "pytest": { - "hashes": [ - "sha256:1cd09785c0a50f9af72220dd12aa78cfa49cbffc356c61eab009ca189e018a33", - "sha256:d010e24666435b39a4cf48740b039885642b6c273a3f77be3e7e03554d2806b7" - ], - "index": "pypi", - "version": "==6.1.0" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" - ], - "version": "==0.10.2" - }, - "traitlets": { - "hashes": [ - "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396", - "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426" - ], - "version": "==5.0.5" - }, - "wcwidth": { - "hashes": [ - "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", - "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" - ], - "version": "==0.2.5" - } - } -} diff --git a/coredns/README.md b/coredns/README.md deleted file mode 100644 index 18f5691..0000000 --- a/coredns/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# CoreDNS Operator - -[CoreDNS][] is a flexible, plugin-based DNS server, and is the recommended -solution for providing DNS to Kubernetes services within the cluster. -This operator enables integration with [Charmed Kubernetes][] via a -cross-model relation and allows for more customization than provided by the -deployment of CoreDNS provided by default by Charmed Kubernetes. - -More information on using this operator with Charmed Kubernetes can be found -[here](https://ubuntu.com/kubernetes/docs/cdk-addons#coredns), and bugs should -be filed [here](https://bugs.launchpad.net/charmed-kubernetes). - - -[CoreDNS]: https://coredns.io/ -[Charmed Kubernetes]: https://ubuntu.com/kubernetes/docs diff --git a/coredns/charmcraft.yaml b/coredns/charmcraft.yaml deleted file mode 100644 index 8ddda8d..0000000 --- a/coredns/charmcraft.yaml +++ /dev/null @@ -1,6 +0,0 @@ -type: charm -parts: - charm: - build-packages: [git] - prime: - - ./files/* diff --git a/coredns/config.yaml b/coredns/config.yaml deleted file mode 100644 index d502510..0000000 --- a/coredns/config.yaml +++ /dev/null @@ -1,38 +0,0 @@ -options: - domain: - description: The local domain for cluster DNS. - type: string - default: cluster.local - forward: - description: Where to forward non-cluster addresses. - type: string - default: /etc/resolv.conf - extra_servers: - description: Any additional servers to add to the Corefile. - type: string - default: '' - corefile: - description: >- - Configuration file to use for CoreDNS. This is interpreted as a Python - string. Template which will be given the `domain` and `forward` configs as - its context. - type: string - default: | - .:53 { - errors - health { - lameduck 5s - } - ready - kubernetes ${domain} in-addr.arpa ip6.arpa { - fallthrough in-addr.arpa ip6.arpa - pods insecure - } - prometheus :9153 - forward . ${forward} - cache 30 - loop - reload - loadbalance - } - ${extra_servers} diff --git a/coredns/dispatch b/coredns/dispatch deleted file mode 100755 index fe31c05..0000000 --- a/coredns/dispatch +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -JUJU_DISPATCH_PATH="${JUJU_DISPATCH_PATH:-$0}" PYTHONPATH=lib:venv ./src/charm.py diff --git a/coredns/hooks/install b/coredns/hooks/install deleted file mode 100755 index fe31c05..0000000 --- a/coredns/hooks/install +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -JUJU_DISPATCH_PATH="${JUJU_DISPATCH_PATH:-$0}" PYTHONPATH=lib:venv ./src/charm.py diff --git a/coredns/hooks/start b/coredns/hooks/start deleted file mode 100755 index fe31c05..0000000 --- a/coredns/hooks/start +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -JUJU_DISPATCH_PATH="${JUJU_DISPATCH_PATH:-$0}" PYTHONPATH=lib:venv ./src/charm.py diff --git a/coredns/hooks/upgrade-charm b/coredns/hooks/upgrade-charm deleted file mode 100755 index fe31c05..0000000 --- a/coredns/hooks/upgrade-charm +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -JUJU_DISPATCH_PATH="${JUJU_DISPATCH_PATH:-$0}" PYTHONPATH=lib:venv ./src/charm.py diff --git a/coredns/icon.svg b/coredns/icon.svg deleted file mode 100644 index a5bac8f..0000000 --- a/coredns/icon.svg +++ /dev/null @@ -1 +0,0 @@ -CoreDNS_Colour_Icon \ No newline at end of file diff --git a/coredns/metadata.yaml b/coredns/metadata.yaml deleted file mode 100644 index 6db0b27..0000000 --- a/coredns/metadata.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: coredns -summary: CoreDNS -maintainers: - - Cory Johns -description: | - CoreDNS provides DNS resolution for Kubernetes. -tags: - - networking -series: - - kubernetes -provides: - dns-provider: - interface: kube-dns -requires: {} -peers: {} -resources: - coredns-image: - type: oci-image - description: 'CoreDNS image' - upstream-source: coredns/coredns:1.6.7 -min-juju-version: 2.8.2 diff --git a/coredns/requirements.txt b/coredns/requirements.txt deleted file mode 100644 index d0569c4..0000000 --- a/coredns/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ --i https://pypi.org/simple -git+https://github.com/juju-solutions/resource-oci-image/@c5778285d332edf3d9a538f9d0c06154b7ec1b0b#egg=oci-image -ops==0.10.0 -pyyaml==5.3.1 diff --git a/coredns/revision b/coredns/revision deleted file mode 100644 index c227083..0000000 --- a/coredns/revision +++ /dev/null @@ -1 +0,0 @@ -0 \ No newline at end of file diff --git a/coredns/src/charm.py b/coredns/src/charm.py deleted file mode 100755 index 6e4128e..0000000 --- a/coredns/src/charm.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env python3 - -import logging -from string import Template - -from ops.charm import CharmBase -from ops.main import main -from ops.model import ActiveStatus, MaintenanceStatus, WaitingStatus - -from oci_image import OCIImageResource, OCIImageResourceError - - -class CoreDNSCharm(CharmBase): - def __init__(self, *args): - super().__init__(*args) - if not self.unit.is_leader(): - # We can't do anything useful when not the leader, so do nothing. - self.model.unit.status = WaitingStatus('Waiting for leadership') - return - self.log = logging.getLogger(__name__) - self.image = OCIImageResource(self, 'coredns-image') - for event in [self.on.install, - self.on.leader_elected, - self.on.upgrade_charm, - self.on.config_changed]: - self.framework.observe(event, self.main) - self.framework.observe(self.on.dns_provider_relation_joined, self.provide_dns) - - def main(self, event): - try: - image_details = self.image.fetch() - except OCIImageResourceError as e: - self.model.unit.status = e.status - return - - self.model.unit.status = MaintenanceStatus('Setting pod spec') - - corefile = Template(self.model.config['corefile']) - corefile = corefile.safe_substitute(self.model.config) - - # Adapted from coredns.yaml.sed in https://github.com/coredns/ at 75a1cad - self.model.pod.set_spec({ - 'version': 3, - 'service': { - 'updateStrategy': { - 'type': 'RollingUpdate', - 'rollingUpdate': {'maxUnavailable': 1}, - }, - 'annotations': { - 'prometheus.io/port': "9153", - 'prometheus.io/scrape': "true", - }, - }, - # Dropped by a regression; see: - # https://bugs.launchpad.net/juju/+bug/1895886 - # 'priorityClassName': 'system-cluster-critical', - 'containers': [{ - 'name': 'coredns', - 'imageDetails': image_details, - 'imagePullPolicy': 'IfNotPresent', - 'args': ['-conf', '/etc/coredns/Corefile'], - 'volumeConfig': [{ - 'name': 'config-volume', - 'mountPath': '/etc/coredns', - # Not supported - # 'readOnly': True, - 'files': [{ - 'path': 'Corefile', - 'mode': 0o444, - 'content': corefile, - }], - }], - 'ports': [ - { - 'name': 'dns', - 'containerPort': 53, - 'protocol': 'UDP', - }, - { - 'name': 'dns-tcp', - 'containerPort': 53, - 'protocol': 'TCP', - }, - { - 'name': 'metrics', - 'containerPort': 9153, - 'protocol': 'TCP', - }, - ], - # Can't be specified by the charm yet; see: - # https://bugs.launchpad.net/juju/+bug/1893123 - # 'resources': { - # 'limits': {'memory': '170Mi'}, - # 'requests': {'cpu': '100m', 'memory': '70Mi'}, - # }, - 'kubernetes': { - 'securityContext': { - 'allowPrivilegeEscalation': False, - 'capabilities': { - 'add': ['NET_BIND_SERVICE'], - 'drop': ['all'], - }, - 'readOnlyRootFilesystem': True, - }, - 'livenessProbe': { - 'httpGet': { - 'path': '/health', - 'port': 8080, - 'scheme': 'HTTP', - }, - 'initialDelaySeconds': 60, - 'timeoutSeconds': 5, - 'successThreshold': 1, - 'failureThreshold': 5, - }, - 'readinessProbe': { - 'httpGet': { - 'path': '/ready', - 'port': 8181, - 'scheme': 'HTTP', - }, - }, - }, - }], - 'serviceAccount': { - 'roles': [{ - 'global': True, - 'rules': [ - { - 'apigroups': ['discovery.k8s.io'], - 'resources': [ - 'endpointslices', - ], - 'verbs': ['list', 'watch'], - }, - { - 'apigroups': [''], - 'resources': [ - 'endpoints', - 'services', - 'pods', - 'namespaces', - ], - 'verbs': ['list', 'watch'], - }, - { - 'apigroups': [''], - 'resources': ['nodes'], - 'verbs': ['get'], - }, - ], - }], - }, - 'kubernetesResources': { - 'pod': { - 'dnsPolicy': 'Default', - # Not yet supported by Juju; see: - # https://bugs.launchpad.net/juju/+bug/1895887 - # 'tolerations': [{ - # 'key': 'CriticalAddonsOnly', - # 'operator': 'Exists', - # }], - # 'affinity': { - # 'podAntiAffinity': { - # 'preferredDuringScheduling' + - # 'IgnoredDuringExecution': [{ - # 'weight': 100, - # 'podAffinityTerm': { - # 'labelSelector': { - # 'matchExpressions': [{ - # 'key': 'k8s-app', - # 'operator': 'In', - # 'values': ["kube-dns"], - # }], - # }, - # 'topologyKey': 'kubernetes.io/hostname', - # }, - # }], - # }, - # }, - # Can be done by the operator via placement (--to), but can't - # be specified by the charm yet, per same bug as above. - # 'nodeSelector': { - # 'kubernetes.io/os': 'linux', - # }, - } - } - }) - self.model.unit.status = ActiveStatus() - - def provide_dns(self, event): - provided_data = event.relation.data[self.unit] - if not provided_data.get('ingress-address'): - event.defer() - return - provided_data.update({ - 'domain': self.model.config['domain'], - 'sdn-ip': str(provided_data['ingress-address']), - 'port': "53", - }) - - -if __name__ == "__main__": - main(CoreDNSCharm) diff --git a/coredns/tests/func/test_deploy.py b/coredns/tests/func/test_deploy.py deleted file mode 100644 index 1497cc3..0000000 --- a/coredns/tests/func/test_deploy.py +++ /dev/null @@ -1,51 +0,0 @@ -import subprocess -from pathlib import Path -from time import sleep - -import pytest - - -CHARM_DIR = Path(__file__).parent.parent.parent.resolve() -SPEC_FILE = Path(__file__).parent / 'validate-dns-spec.yaml' - - -def test_charm(): - model = run('juju', 'switch').split('/')[-1] - coredns_ready = run( - 'kubectl', 'get', 'pod', '-n', model, '-l', 'juju-app=coredns', - '-o', 'jsonpath={..status.containerStatuses[0].ready}') - assert coredns_ready == 'true' - run('kubectl', 'apply', '-f', SPEC_FILE) - try: - wait_for_output('kubectl', 'get', 'pod/validate-dns', - expected='Running') - for name in ("www.ubuntu.com", "kubernetes.default.svc.cluster.local"): - run('kubectl', 'exec', 'validate-dns', '--', 'nslookup', name) - finally: - run('kubectl', 'delete', '-f', SPEC_FILE) - - -def run(*args): - args = [str(a) for a in args] - try: - res = subprocess.run(args, - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - return res.stdout.decode('utf8').strip() - except subprocess.CalledProcessError as e: - pytest.fail(f'Command {args} failed ({e.returncode}):\n' - f'stdout:\n{e.stdout.decode("utf8")}\n' - f'stderr:\n{e.stderr.decode("utf8")}\n') - - -def wait_for_output(*args, expected='', timeout=3 * 60): - args = [str(a) for a in args] - output = None - for attempt in range(int(timeout / 5)): - output = run(*args) - if expected in output: - break - sleep(5) - else: - pytest.fail(f'Timed out waiting for "{expected}" from {args}:\n{output}') diff --git a/coredns/tests/func/validate-dns-spec.yaml b/coredns/tests/func/validate-dns-spec.yaml deleted file mode 100644 index cfe5d27..0000000 --- a/coredns/tests/func/validate-dns-spec.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: validate-dns -spec: - containers: - - name: busybox - image: busybox - imagePullPolicy: IfNotPresent - args: ['sleep', '3600'] - restartPolicy: Always diff --git a/coredns/tests/unit/test_charm.py b/coredns/tests/unit/test_charm.py deleted file mode 100644 index f0f95fd..0000000 --- a/coredns/tests/unit/test_charm.py +++ /dev/null @@ -1,42 +0,0 @@ -import pytest - -from ops.model import ActiveStatus, BlockedStatus, WaitingStatus -from ops.testing import Harness -import yaml - -from charm import CoreDNSCharm - - -if yaml.__with_libyaml__: - _DefaultDumper = yaml.CSafeDumper -else: - _DefaultDumper = yaml.SafeDumper - - -@pytest.fixture -def harness(): - return Harness(CoreDNSCharm) - - -def test_not_leader(harness): - harness.begin() - assert isinstance(harness.charm.model.unit.status, WaitingStatus) - - -def test_missing_image(harness): - harness.set_leader(True) - harness.begin_with_initial_hooks() - assert isinstance(harness.charm.model.unit.status, BlockedStatus) - - -def test_main(harness): - harness.set_leader(True) - harness.add_oci_resource('coredns-image', { - 'registrypath': 'coredns/coredns:1.6.7', - 'username': '', - 'password': '', - }) - harness.begin_with_initial_hooks() - assert isinstance(harness.charm.model.unit.status, ActiveStatus) - # confirm that we can serialize the pod spec - yaml.dump(harness.get_pod_spec(), Dumper=_DefaultDumper) diff --git a/coredns/tox.ini b/coredns/tox.ini deleted file mode 100644 index a3006a7..0000000 --- a/coredns/tox.ini +++ /dev/null @@ -1,27 +0,0 @@ -[flake8] -max-line-length = 88 - -[tox] -skipsdist = True -envlist = lint,unit - -[testenv] -basepython = python3 -setenv = - PYTHONPATH={toxinidir}/src - PYTHONBREAKPOINT=ipdb.set_trace -passenv = HOME -deps = pipenv -commands = - pipenv install --dev --ignore-pipfile - pipenv run pytest --tb native -s {posargs:tests/unit} - -[testenv:lint] -commands = - pipenv install --dev --ignore-pipfile - pipenv run flake8 {toxinidir}/src {toxinidir}/tests - -[testenv:func] -commands = - pipenv install --dev --ignore-pipfile - pipenv run pytest --tb native -s {posargs:tests/func} diff --git a/coredns/venv/PyYAML-5.3.1.dist-info/INSTALLER b/coredns/venv/PyYAML-5.3.1.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/coredns/venv/PyYAML-5.3.1.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/coredns/venv/PyYAML-5.3.1.dist-info/LICENSE b/coredns/venv/PyYAML-5.3.1.dist-info/LICENSE deleted file mode 100644 index 3d82c28..0000000 --- a/coredns/venv/PyYAML-5.3.1.dist-info/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2017-2020 Ingy döt Net -Copyright (c) 2006-2016 Kirill Simonov - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/coredns/venv/PyYAML-5.3.1.dist-info/METADATA b/coredns/venv/PyYAML-5.3.1.dist-info/METADATA deleted file mode 100644 index a70dd20..0000000 --- a/coredns/venv/PyYAML-5.3.1.dist-info/METADATA +++ /dev/null @@ -1,41 +0,0 @@ -Metadata-Version: 2.1 -Name: PyYAML -Version: 5.3.1 -Summary: YAML parser and emitter for Python -Home-page: https://github.com/yaml/pyyaml -Author: Kirill Simonov -Author-email: xi@resolvent.net -License: MIT -Download-URL: https://pypi.org/project/PyYAML/ -Platform: Any -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Cython -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: Text Processing :: Markup -Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* - -YAML is a data serialization format designed for human readability -and interaction with scripting languages. PyYAML is a YAML parser -and emitter for Python. - -PyYAML features a complete YAML 1.1 parser, Unicode support, pickle -support, capable extension API, and sensible error messages. PyYAML -supports standard YAML tags and provides Python-specific tags that -allow to represent an arbitrary Python object. - -PyYAML is applicable for a broad range of tasks from complex -configuration files to object serialization and persistence. - diff --git a/coredns/venv/PyYAML-5.3.1.dist-info/RECORD b/coredns/venv/PyYAML-5.3.1.dist-info/RECORD deleted file mode 100644 index a01343d..0000000 --- a/coredns/venv/PyYAML-5.3.1.dist-info/RECORD +++ /dev/null @@ -1,41 +0,0 @@ -PyYAML-5.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -PyYAML-5.3.1.dist-info/LICENSE,sha256=xAESRJ8lS5dTBFklJIMT6ScO-jbSJrItgtTMbEPFfyk,1101 -PyYAML-5.3.1.dist-info/METADATA,sha256=xTsZFjd8T4M-5rC2M3BHgx_KTTpEPy5vFDIXrbzRXPQ,1758 -PyYAML-5.3.1.dist-info/RECORD,, -PyYAML-5.3.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -PyYAML-5.3.1.dist-info/WHEEL,sha256=hzx2-39jWfx-No5BPGm7YN661ryRYBuLP8gZdbxDo8I,103 -PyYAML-5.3.1.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11 -yaml/__init__.py,sha256=XFUNbKTg4afAd0BETjGQ1mKQ97_g5jbE1C0WoKc74dc,13170 -yaml/__pycache__/__init__.cpython-38.pyc,, -yaml/__pycache__/composer.cpython-38.pyc,, -yaml/__pycache__/constructor.cpython-38.pyc,, -yaml/__pycache__/cyaml.cpython-38.pyc,, -yaml/__pycache__/dumper.cpython-38.pyc,, -yaml/__pycache__/emitter.cpython-38.pyc,, -yaml/__pycache__/error.cpython-38.pyc,, -yaml/__pycache__/events.cpython-38.pyc,, -yaml/__pycache__/loader.cpython-38.pyc,, -yaml/__pycache__/nodes.cpython-38.pyc,, -yaml/__pycache__/parser.cpython-38.pyc,, -yaml/__pycache__/reader.cpython-38.pyc,, -yaml/__pycache__/representer.cpython-38.pyc,, -yaml/__pycache__/resolver.cpython-38.pyc,, -yaml/__pycache__/scanner.cpython-38.pyc,, -yaml/__pycache__/serializer.cpython-38.pyc,, -yaml/__pycache__/tokens.cpython-38.pyc,, -yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883 -yaml/constructor.py,sha256=O3Uaf0_J_5GQBoeI9ZNhpJAhtdagr_X2HzDgGbZOMnw,28627 -yaml/cyaml.py,sha256=LiMkvchNonfoy1F6ec9L2BiUz3r0bwF4hympASJX1Ic,3846 -yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837 -yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006 -yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533 -yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445 -yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061 -yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440 -yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495 -yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794 -yaml/representer.py,sha256=82UM3ZxUQKqsKAF4ltWOxCS6jGPIFtXpGs7mvqyv4Xs,14184 -yaml/resolver.py,sha256=DJCjpQr8YQCEYYjKEYqTl0GrsZil2H4aFOI9b0Oe-U4,8970 -yaml/scanner.py,sha256=KeQIKGNlSyPE8QDwionHxy9CgbqE5teJEz05FR9-nAg,51277 -yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165 -yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573 diff --git a/coredns/venv/PyYAML-5.3.1.dist-info/REQUESTED b/coredns/venv/PyYAML-5.3.1.dist-info/REQUESTED deleted file mode 100644 index e69de29..0000000 diff --git a/coredns/venv/PyYAML-5.3.1.dist-info/WHEEL b/coredns/venv/PyYAML-5.3.1.dist-info/WHEEL deleted file mode 100644 index bb3795f..0000000 --- a/coredns/venv/PyYAML-5.3.1.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.36.2) -Root-Is-Purelib: false -Tag: cp38-cp38-linux_x86_64 - diff --git a/coredns/venv/PyYAML-5.3.1.dist-info/top_level.txt b/coredns/venv/PyYAML-5.3.1.dist-info/top_level.txt deleted file mode 100644 index e6475e9..0000000 --- a/coredns/venv/PyYAML-5.3.1.dist-info/top_level.txt +++ /dev/null @@ -1,2 +0,0 @@ -_yaml -yaml diff --git a/coredns/venv/__pycache__/oci_image.cpython-38.pyc b/coredns/venv/__pycache__/oci_image.cpython-38.pyc deleted file mode 100644 index 031a742..0000000 Binary files a/coredns/venv/__pycache__/oci_image.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/oci_image-1.0.0.dist-info/INSTALLER b/coredns/venv/oci_image-1.0.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/coredns/venv/oci_image-1.0.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/coredns/venv/oci_image-1.0.0.dist-info/METADATA b/coredns/venv/oci_image-1.0.0.dist-info/METADATA deleted file mode 100644 index b21b997..0000000 --- a/coredns/venv/oci_image-1.0.0.dist-info/METADATA +++ /dev/null @@ -1,63 +0,0 @@ -Metadata-Version: 2.1 -Name: oci-image -Version: 1.0.0 -Summary: Helper for dealing with OCI Image resources in the charm operator framework -Home-page: https://github.com/juju-solutions/resource-oci-image -Author: Cory Johns -Author-email: johnsca@gmail.com -License: Apache License 2.0 -Platform: UNKNOWN - -# OCI Image Resource helper - -This is a helper for working with OCI image resources in the charm operator -framework. - -## Installation - -Add it to your `requirements.txt`. Since it's not in PyPI, you'll need to use -the GitHub archive URL (or `git+` URL, if you want to pin to a specific commit): - -``` -https://github.com/juju-solutions/resource-oci-image/archive/master.zip -``` - -## Usage - -The `OCIImageResource` class will wrap the framework resource for the given -resource name, and calling `fetch` on it will either return the image info -or raise an `OCIImageResourceError` if it can't fetch or parse the image -info. The exception will have a `status` attribute you can use directly, -or a `status_message` attribute if you just want that. - -Example usage: - -```python -from ops.charm import CharmBase -from ops.main import main -from oci_image import OCIImageResource, OCIImageResourceError - -class MyCharm(CharmBase): - def __init__(self, *args): - super().__init__(*args) - self.image = OCIImageResource(self, 'resource-name') - self.framework.observe(self.on.start, self.on_start) - - def on_start(self, event): - try: - image_info = self.image.fetch() - except OCIImageResourceError as e: - self.model.unit.status = e.status - event.defer() - return - - self.model.pod.set_spec({'containers': [{ - 'name': 'my-charm', - 'imageDetails': image_info, - }]}) - -if __name__ == "__main__": - main(MyCharm) -``` - - diff --git a/coredns/venv/oci_image-1.0.0.dist-info/RECORD b/coredns/venv/oci_image-1.0.0.dist-info/RECORD deleted file mode 100644 index 487344e..0000000 --- a/coredns/venv/oci_image-1.0.0.dist-info/RECORD +++ /dev/null @@ -1,9 +0,0 @@ -__pycache__/oci_image.cpython-38.pyc,, -oci_image-1.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -oci_image-1.0.0.dist-info/METADATA,sha256=QIpPa4JcSPa_Ci0n-DaCNp4PkKovZudFW8FnpnauJnQ,1808 -oci_image-1.0.0.dist-info/RECORD,, -oci_image-1.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -oci_image-1.0.0.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92 -oci_image-1.0.0.dist-info/direct_url.json,sha256=sUsaIeKXs7oqCE-NdmqTsNJ8rmr97YMi0wuRNVObj0Y,215 -oci_image-1.0.0.dist-info/top_level.txt,sha256=M4dLaObLx7irI4EO-A4_VJP_b-A6dDD7hB5QyVKdHOY,10 -oci_image.py,sha256=c75VR2vSmOp9pPTP2cnsxo23CqhhFbRtnIOtMjzDyXY,1794 diff --git a/coredns/venv/oci_image-1.0.0.dist-info/REQUESTED b/coredns/venv/oci_image-1.0.0.dist-info/REQUESTED deleted file mode 100644 index e69de29..0000000 diff --git a/coredns/venv/oci_image-1.0.0.dist-info/WHEEL b/coredns/venv/oci_image-1.0.0.dist-info/WHEEL deleted file mode 100644 index 385faab..0000000 --- a/coredns/venv/oci_image-1.0.0.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.36.2) -Root-Is-Purelib: true -Tag: py3-none-any - diff --git a/coredns/venv/oci_image-1.0.0.dist-info/direct_url.json b/coredns/venv/oci_image-1.0.0.dist-info/direct_url.json deleted file mode 100644 index 56c97aa..0000000 --- a/coredns/venv/oci_image-1.0.0.dist-info/direct_url.json +++ /dev/null @@ -1 +0,0 @@ -{"url": "https://github.com/juju-solutions/resource-oci-image/", "vcs_info": {"commit_id": "c5778285d332edf3d9a538f9d0c06154b7ec1b0b", "requested_revision": "c5778285d332edf3d9a538f9d0c06154b7ec1b0b", "vcs": "git"}} \ No newline at end of file diff --git a/coredns/venv/oci_image-1.0.0.dist-info/top_level.txt b/coredns/venv/oci_image-1.0.0.dist-info/top_level.txt deleted file mode 100644 index cd69623..0000000 --- a/coredns/venv/oci_image-1.0.0.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -oci_image diff --git a/coredns/venv/oci_image.py b/coredns/venv/oci_image.py deleted file mode 100644 index f4d3818..0000000 --- a/coredns/venv/oci_image.py +++ /dev/null @@ -1,53 +0,0 @@ -from pathlib import Path - -import yaml -from ops.framework import Object -from ops.model import BlockedStatus, ModelError - - -class OCIImageResource(Object): - def __init__(self, charm, resource_name): - super().__init__(charm, resource_name) - self.resource_name = resource_name - - def fetch(self): - try: - resource_path = self.model.resources.fetch(self.resource_name) - except ModelError as e: - raise MissingResourceError(self.resource_name) from e - if not resource_path.exists(): - raise MissingResourceError(self.resource_name) - resource_text = Path(resource_path).read_text() - if not resource_text: - raise MissingResourceError(self.resource_name) - try: - resource_data = yaml.safe_load(resource_text) - except yaml.YAMLError as e: - raise InvalidResourceError(self.resource_name) from e - else: - # Translate the data from the format used by the charm store to the - # format used by the Juju K8s pod spec, since that is how this is - # typically used. - return { - 'imagePath': resource_data['registrypath'], - 'username': resource_data['username'], - 'password': resource_data['password'], - } - - -class OCIImageResourceError(ModelError): - status_type = BlockedStatus - status_message = 'Resource error' - - def __init__(self, resource_name): - super().__init__(resource_name) - self.status = self.status_type( - f'{self.status_message}: {resource_name}') - - -class MissingResourceError(OCIImageResourceError): - status_message = 'Missing resource' - - -class InvalidResourceError(OCIImageResourceError): - status_message = 'Invalid resource' diff --git a/coredns/venv/ops-0.10.0.dist-info/INSTALLER b/coredns/venv/ops-0.10.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/coredns/venv/ops-0.10.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/coredns/venv/ops-0.10.0.dist-info/LICENSE.txt b/coredns/venv/ops-0.10.0.dist-info/LICENSE.txt deleted file mode 100644 index d645695..0000000 --- a/coredns/venv/ops-0.10.0.dist-info/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/coredns/venv/ops-0.10.0.dist-info/METADATA b/coredns/venv/ops-0.10.0.dist-info/METADATA deleted file mode 100644 index a8a0e8e..0000000 --- a/coredns/venv/ops-0.10.0.dist-info/METADATA +++ /dev/null @@ -1,167 +0,0 @@ -Metadata-Version: 2.1 -Name: ops -Version: 0.10.0 -Summary: The Python library behind great charms -Home-page: https://github.com/canonical/operator -Author: The Charmcraft team at Canonical Ltd. -Author-email: charmcraft@lists.launchpad.net -License: Apache-2.0 -Platform: UNKNOWN -Classifier: Programming Language :: Python :: 3 -Classifier: License :: OSI Approved :: Apache Software License -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: System Administrators -Classifier: Operating System :: MacOS :: MacOS X -Classifier: Operating System :: POSIX :: Linux -Requires-Python: >=3.5 -Description-Content-Type: text/markdown -Requires-Dist: PyYAML - -# The Operator Framework - -The Operator Framework provides a simple, lightweight, and powerful way of -writing Juju charms, the best way to encapsulate operational experience in code. - -The framework will help you to: - -* model the integration of your services -* manage the lifecycle of your application -* create reusable and scalable components -* keep your code simple and readable - -## Getting Started - -Charms written using the operator framework are just Python code. The intention -is for it to feel very natural for somebody used to coding in Python, and -reasonably easy to pick up for somebody who might be a domain expert but not -necessarily a pythonista themselves. - -The dependencies of the operator framework are kept as minimal as possible; -currently that's Python 3.5 or greater, and `PyYAML` (both are included by -default in Ubuntu's cloud images from 16.04 on). - - -## A Quick Introduction - -Operator framework charms are just Python code. The entry point to your charm is -a particular Python file. It could be anything that makes sense to your project, -but let's assume this is `src/charm.py`. This file must be executable (and it -must have the appropriate shebang line). - -You need the usual `metadata.yaml` and (probably) `config.yaml` files, and a -`requirements.txt` for any Python dependencies. In other words, your project -might look like this: - -``` -my-charm -├── config.yaml -├── metadata.yaml -├── requirements.txt -└── src/ - └── charm.py -``` - -`src/charm.py` here is the entry point to your charm code. At a minimum, it -needs to define a subclass of `CharmBase` and pass that into the framework's -`main` function: - -```python -from ops.charm import CharmBase -from ops.main import main - -class MyCharm(CharmBase): - def __init__(self, *args): - super().__init__(*args) - self.framework.observe(self.on.start, self.on_start) - - def on_start(self, event): - # Handle the start event here. - -if __name__ == "__main__": - main(MyCharm) -``` - -That should be enough for you to be able to run - -``` -$ charmcraft build -Done, charm left in 'my-charm.charm' -$ juju deploy ./my-charm.charm -``` - -> 🛈 More information on [`charmcraft`](https://pypi.org/project/charmcraft/) can -> also be found on its [github page](https://github.com/canonical/charmcraft). - -Happy charming! - -## Testing your charms - -The operator framework provides a testing harness, so that you can test that -your charm does the right thing when presented with different scenarios, without -having to have a full deployment to do so. `pydoc3 ops.testing` has the details -for that, including this example: - -```python -harness = Harness(MyCharm) -# Do initial setup here -relation_id = harness.add_relation('db', 'postgresql') -# Now instantiate the charm to see events as the model changes -harness.begin() -harness.add_relation_unit(relation_id, 'postgresql/0') -harness.update_relation_data(relation_id, 'postgresql/0', {'key': 'val'}) -# Check that charm has properly handled the relation_joined event for postgresql/0 -self.assertEqual(harness.charm. ...) -``` - -## Talk to us - -If you need help, have ideas, or would just like to chat with us, reach out on -IRC: we're in [#smooth-operator] on freenode (or try the [webchat]). - -We also pay attention to Juju's [discourse]; most discussion at this -stage is on IRC, however. - -You can also deep dive into the [API docs] if that's your thing. - -[webchat]: https://webchat.freenode.net/#smooth-operator -[#smooth-operator]: irc://chat.freenode.net/%23smooth-operator -[discourse]: https://discourse.juju.is/c/charming -[API docs]: https://ops.rtfd.io/ - -## Operator Framework development - -If you want to work in the framework *itself* you will need Python >= 3.5 and -the dependencies declared in `requirements-dev.txt` installed in your system. -Or you can use a virtualenv: - - virtualenv --python=python3 env - source env/bin/activate - pip install -r requirements-dev.txt - -Then you can try `./run_tests`, it should all go green. - -If you see the error `yaml does not have libyaml extensions, using slower pure -Python yaml`, you need to reinstall pyyaml with the correct extensions: - - apt-get install libyaml-dev - pip install --force-reinstall --no-cache-dir pyyaml - -If you want to build the documentation you'll need the requirements from -`docs/requirements.txt`, or in your virtualenv - - pip install -r docs/requirements.txt - -and then you can run `./build_docs`. - - diff --git a/coredns/venv/ops-0.10.0.dist-info/RECORD b/coredns/venv/ops-0.10.0.dist-info/RECORD deleted file mode 100644 index fe067e0..0000000 --- a/coredns/venv/ops-0.10.0.dist-info/RECORD +++ /dev/null @@ -1,29 +0,0 @@ -ops-0.10.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -ops-0.10.0.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 -ops-0.10.0.dist-info/METADATA,sha256=AI7mL-PWkkYQ4f_NCulM5VcIQrMskxPIYp108DZrOcA,5577 -ops-0.10.0.dist-info/RECORD,, -ops-0.10.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -ops-0.10.0.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92 -ops-0.10.0.dist-info/top_level.txt,sha256=enC05wWafSg8iDKIvj3gvtAtEP2kYCyN5Gmd689q-_I,4 -ops/__init__.py,sha256=WaHb0dfp1KEe6jFV8Pm_mcdJ3ModiWujnQ6xLjNzPNQ,819 -ops/__pycache__/__init__.cpython-38.pyc,, -ops/__pycache__/charm.cpython-38.pyc,, -ops/__pycache__/framework.cpython-38.pyc,, -ops/__pycache__/jujuversion.cpython-38.pyc,, -ops/__pycache__/log.cpython-38.pyc,, -ops/__pycache__/main.cpython-38.pyc,, -ops/__pycache__/model.cpython-38.pyc,, -ops/__pycache__/storage.cpython-38.pyc,, -ops/__pycache__/testing.cpython-38.pyc,, -ops/__pycache__/version.cpython-38.pyc,, -ops/charm.py,sha256=i1fcd-pMzRV6f9AfMy0S_Jr_rZso3s9Xi-5GZWEs3nc,22512 -ops/framework.py,sha256=T9PWR4FXBI6Yd3XGwwNO51rJlyMUeO5vPdd4GmEjdzY,38298 -ops/jujuversion.py,sha256=T5KafqBHbQiHJ1OVoVbseUnZz7og4gPUz7CayXcHddk,3845 -ops/lib/__init__.py,sha256=7i2EN1jCUkVZT5NCi_q_ilBBzpCkWaW9mnBc3vBYCns,9188 -ops/lib/__pycache__/__init__.cpython-38.pyc,, -ops/log.py,sha256=7jNn71--WpFngrZIwnJoaTRiaVrNVkLHK2enVu_VRA8,1860 -ops/main.py,sha256=TcOAS3VE1nMt-jF9uUzoyDWGTNl-OoAkS7XqQraWH3c,15375 -ops/model.py,sha256=katD2gQc35VArVMfGdI2AjPobFegQjShmDqVCKeLXZc,46796 -ops/storage.py,sha256=dal0athxe35cnWE8ol9N7nEUQDMcphDgRrQrmyGQDoA,11859 -ops/testing.py,sha256=HRjgq2ikVijGRMjVN2g-HJr8oQJ0ul8QEUUZv9D2_go,34727 -ops/version.py,sha256=6wsm0bsNX30wL9YmCZai2X5ISKQZYBIFJAbgmBn2Ri4,47 diff --git a/coredns/venv/ops-0.10.0.dist-info/REQUESTED b/coredns/venv/ops-0.10.0.dist-info/REQUESTED deleted file mode 100644 index e69de29..0000000 diff --git a/coredns/venv/ops-0.10.0.dist-info/WHEEL b/coredns/venv/ops-0.10.0.dist-info/WHEEL deleted file mode 100644 index b552003..0000000 --- a/coredns/venv/ops-0.10.0.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.34.2) -Root-Is-Purelib: true -Tag: py3-none-any - diff --git a/coredns/venv/ops-0.10.0.dist-info/top_level.txt b/coredns/venv/ops-0.10.0.dist-info/top_level.txt deleted file mode 100644 index 2d81d3b..0000000 --- a/coredns/venv/ops-0.10.0.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -ops diff --git a/coredns/venv/ops/__init__.py b/coredns/venv/ops/__init__.py deleted file mode 100644 index f17b296..0000000 --- a/coredns/venv/ops/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2020 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""The Operator Framework.""" - -from .version import version as __version__ # noqa: F401 (imported but unused) - -# Import here the bare minimum to break the circular import between modules -from . import charm # noqa: F401 (imported but unused) diff --git a/coredns/venv/ops/__pycache__/__init__.cpython-38.pyc b/coredns/venv/ops/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index c37336c..0000000 Binary files a/coredns/venv/ops/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/__pycache__/charm.cpython-38.pyc b/coredns/venv/ops/__pycache__/charm.cpython-38.pyc deleted file mode 100644 index 0cdaada..0000000 Binary files a/coredns/venv/ops/__pycache__/charm.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/__pycache__/framework.cpython-38.pyc b/coredns/venv/ops/__pycache__/framework.cpython-38.pyc deleted file mode 100644 index 37a070e..0000000 Binary files a/coredns/venv/ops/__pycache__/framework.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/__pycache__/jujuversion.cpython-38.pyc b/coredns/venv/ops/__pycache__/jujuversion.cpython-38.pyc deleted file mode 100644 index be159fd..0000000 Binary files a/coredns/venv/ops/__pycache__/jujuversion.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/__pycache__/log.cpython-38.pyc b/coredns/venv/ops/__pycache__/log.cpython-38.pyc deleted file mode 100644 index 83de3b9..0000000 Binary files a/coredns/venv/ops/__pycache__/log.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/__pycache__/main.cpython-38.pyc b/coredns/venv/ops/__pycache__/main.cpython-38.pyc deleted file mode 100644 index f4f10a5..0000000 Binary files a/coredns/venv/ops/__pycache__/main.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/__pycache__/model.cpython-38.pyc b/coredns/venv/ops/__pycache__/model.cpython-38.pyc deleted file mode 100644 index db6dc9e..0000000 Binary files a/coredns/venv/ops/__pycache__/model.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/__pycache__/storage.cpython-38.pyc b/coredns/venv/ops/__pycache__/storage.cpython-38.pyc deleted file mode 100644 index 1bb4a07..0000000 Binary files a/coredns/venv/ops/__pycache__/storage.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/__pycache__/testing.cpython-38.pyc b/coredns/venv/ops/__pycache__/testing.cpython-38.pyc deleted file mode 100644 index 2d9cb29..0000000 Binary files a/coredns/venv/ops/__pycache__/testing.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/__pycache__/version.cpython-38.pyc b/coredns/venv/ops/__pycache__/version.cpython-38.pyc deleted file mode 100644 index 1be0b7a..0000000 Binary files a/coredns/venv/ops/__pycache__/version.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/charm.py b/coredns/venv/ops/charm.py deleted file mode 100644 index d898de8..0000000 --- a/coredns/venv/ops/charm.py +++ /dev/null @@ -1,575 +0,0 @@ -# Copyright 2019-2020 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import enum -import os -import pathlib -import typing - -import yaml - -from ops.framework import Object, EventSource, EventBase, Framework, ObjectEvents -from ops import model - - -def _loadYaml(source): - if yaml.__with_libyaml__: - return yaml.load(source, Loader=yaml.CSafeLoader) - return yaml.load(source, Loader=yaml.SafeLoader) - - -class HookEvent(EventBase): - """A base class for events that trigger because of a Juju hook firing.""" - - -class ActionEvent(EventBase): - """A base class for events that trigger when a user asks for an Action to be run. - - To read the parameters for the action, see the instance variable `params`. - To respond with the result of the action, call `set_results`. To add progress - messages that are visible as the action is progressing use `log`. - - :ivar params: The parameters passed to the action (read by action-get) - """ - - def defer(self): - """Action events are not deferable like other events. - - This is because an action runs synchronously and the user is waiting for the result. - """ - raise RuntimeError('cannot defer action events') - - def restore(self, snapshot: dict) -> None: - """Used by the operator framework to record the action. - - Not meant to be called directly by Charm code. - """ - env_action_name = os.environ.get('JUJU_ACTION_NAME') - event_action_name = self.handle.kind[:-len('_action')].replace('_', '-') - if event_action_name != env_action_name: - # This could only happen if the dev manually emits the action, or from a bug. - raise RuntimeError('action event kind does not match current action') - # Params are loaded at restore rather than __init__ because - # the model is not available in __init__. - self.params = self.framework.model._backend.action_get() - - def set_results(self, results: typing.Mapping) -> None: - """Report the result of the action. - - Args: - results: The result of the action as a Dict - """ - self.framework.model._backend.action_set(results) - - def log(self, message: str) -> None: - """Send a message that a user will see while the action is running. - - Args: - message: The message for the user. - """ - self.framework.model._backend.action_log(message) - - def fail(self, message: str = '') -> None: - """Report that this action has failed. - - Args: - message: Optional message to record why it has failed. - """ - self.framework.model._backend.action_fail(message) - - -class InstallEvent(HookEvent): - """Represents the `install` hook from Juju.""" - - -class StartEvent(HookEvent): - """Represents the `start` hook from Juju.""" - - -class StopEvent(HookEvent): - """Represents the `stop` hook from Juju.""" - - -class RemoveEvent(HookEvent): - """Represents the `remove` hook from Juju. """ - - -class ConfigChangedEvent(HookEvent): - """Represents the `config-changed` hook from Juju.""" - - -class UpdateStatusEvent(HookEvent): - """Represents the `update-status` hook from Juju.""" - - -class UpgradeCharmEvent(HookEvent): - """Represents the `upgrade-charm` hook from Juju. - - This will be triggered when a user has run `juju upgrade-charm`. It is run after Juju - has unpacked the upgraded charm code, and so this event will be handled with new code. - """ - - -class PreSeriesUpgradeEvent(HookEvent): - """Represents the `pre-series-upgrade` hook from Juju. - - This happens when a user has run `juju upgrade-series MACHINE prepare` and - will fire for each unit that is running on the machine, telling them that - the user is preparing to upgrade the Machine's series (eg trusty->bionic). - The charm should take actions to prepare for the upgrade (a database charm - would want to write out a version-independent dump of the database, so that - when a new version of the database is available in a new series, it can be - used.) - Once all units on a machine have run `pre-series-upgrade`, the user will - initiate the steps to actually upgrade the machine (eg `do-release-upgrade`). - When the upgrade has been completed, the :class:`PostSeriesUpgradeEvent` will fire. - """ - - -class PostSeriesUpgradeEvent(HookEvent): - """Represents the `post-series-upgrade` hook from Juju. - - This is run after the user has done a distribution upgrade (or rolled back - and kept the same series). It is called in response to - `juju upgrade-series MACHINE complete`. Charms are expected to do whatever - steps are necessary to reconfigure their applications for the new series. - """ - - -class LeaderElectedEvent(HookEvent): - """Represents the `leader-elected` hook from Juju. - - Juju will trigger this when a new lead unit is chosen for a given application. - This represents the leader of the charm information (not necessarily the primary - of a running application). The main utility is that charm authors can know - that only one unit will be a leader at any given time, so they can do - configuration, etc, that would otherwise require coordination between units. - (eg, selecting a password for a new relation) - """ - - -class LeaderSettingsChangedEvent(HookEvent): - """Represents the `leader-settings-changed` hook from Juju. - - Deprecated. This represents when a lead unit would call `leader-set` to inform - the other units of an application that they have new information to handle. - This has been deprecated in favor of using a Peer relation, and having the - leader set a value in the Application data bag for that peer relation. - (see :class:`RelationChangedEvent`). - """ - - -class CollectMetricsEvent(HookEvent): - """Represents the `collect-metrics` hook from Juju. - - Note that events firing during a CollectMetricsEvent are currently - sandboxed in how they can interact with Juju. To report metrics - use :meth:`.add_metrics`. - """ - - def add_metrics(self, metrics: typing.Mapping, labels: typing.Mapping = None) -> None: - """Record metrics that have been gathered by the charm for this unit. - - Args: - metrics: A collection of {key: float} pairs that contains the - metrics that have been gathered - labels: {key:value} strings that can be applied to the - metrics that are being gathered - """ - self.framework.model._backend.add_metrics(metrics, labels) - - -class RelationEvent(HookEvent): - """A base class representing the various relation lifecycle events. - - Charmers should not be creating RelationEvents directly. The events will be - generated by the framework from Juju related events. Users can observe them - from the various `CharmBase.on[relation_name].relation_*` events. - - Attributes: - relation: The Relation involved in this event - app: The remote application that has triggered this event - unit: The remote unit that has triggered this event. This may be None - if the relation event was triggered as an Application level event - """ - - def __init__(self, handle, relation, app=None, unit=None): - super().__init__(handle) - - if unit is not None and unit.app != app: - raise RuntimeError( - 'cannot create RelationEvent with application {} and unit {}'.format(app, unit)) - - self.relation = relation - self.app = app - self.unit = unit - - def snapshot(self) -> dict: - """Used by the framework to serialize the event to disk. - - Not meant to be called by Charm code. - """ - snapshot = { - 'relation_name': self.relation.name, - 'relation_id': self.relation.id, - } - if self.app: - snapshot['app_name'] = self.app.name - if self.unit: - snapshot['unit_name'] = self.unit.name - return snapshot - - def restore(self, snapshot: dict) -> None: - """Used by the framework to deserialize the event from disk. - - Not meant to be called by Charm code. - """ - self.relation = self.framework.model.get_relation( - snapshot['relation_name'], snapshot['relation_id']) - - app_name = snapshot.get('app_name') - if app_name: - self.app = self.framework.model.get_app(app_name) - else: - self.app = None - - unit_name = snapshot.get('unit_name') - if unit_name: - self.unit = self.framework.model.get_unit(unit_name) - else: - self.unit = None - - -class RelationCreatedEvent(RelationEvent): - """Represents the `relation-created` hook from Juju. - - This is triggered when a new relation to another app is added in Juju. This - can occur before units for those applications have started. All existing - relations should be established before start. - """ - - -class RelationJoinedEvent(RelationEvent): - """Represents the `relation-joined` hook from Juju. - - This is triggered whenever a new unit of a related application joins the relation. - (eg, a unit was added to an existing related app, or a new relation was established - with an application that already had units.) - """ - - -class RelationChangedEvent(RelationEvent): - """Represents the `relation-changed` hook from Juju. - - This is triggered whenever there is a change to the data bucket for a related - application or unit. Look at `event.relation.data[event.unit/app]` to see the - new information. - """ - - -class RelationDepartedEvent(RelationEvent): - """Represents the `relation-departed` hook from Juju. - - This is the inverse of the RelationJoinedEvent, representing when a unit - is leaving the relation (the unit is being removed, the app is being removed, - the relation is being removed). It is fired once for each unit that is - going away. - """ - - -class RelationBrokenEvent(RelationEvent): - """Represents the `relation-broken` hook from Juju. - - If a relation is being removed (`juju remove-relation` or `juju remove-application`), - once all the units have been removed, RelationBrokenEvent will fire to signal - that the relationship has been fully terminated. - """ - - -class StorageEvent(HookEvent): - """Base class representing Storage related events.""" - - -class StorageAttachedEvent(StorageEvent): - """Represents the `storage-attached` hook from Juju. - - Called when new storage is available for the charm to use. - """ - - -class StorageDetachingEvent(StorageEvent): - """Represents the `storage-detaching` hook from Juju. - - Called when storage a charm has been using is going away. - """ - - -class CharmEvents(ObjectEvents): - """The events that are generated by Juju in response to the lifecycle of an application.""" - - install = EventSource(InstallEvent) - start = EventSource(StartEvent) - stop = EventSource(StopEvent) - remove = EventSource(RemoveEvent) - update_status = EventSource(UpdateStatusEvent) - config_changed = EventSource(ConfigChangedEvent) - upgrade_charm = EventSource(UpgradeCharmEvent) - pre_series_upgrade = EventSource(PreSeriesUpgradeEvent) - post_series_upgrade = EventSource(PostSeriesUpgradeEvent) - leader_elected = EventSource(LeaderElectedEvent) - leader_settings_changed = EventSource(LeaderSettingsChangedEvent) - collect_metrics = EventSource(CollectMetricsEvent) - - -class CharmBase(Object): - """Base class that represents the Charm overall. - - Usually this initialization is done by ops.main.main() rather than Charm authors - directly instantiating a Charm. - - Args: - framework: The framework responsible for managing the Model and events for this - Charm. - key: Ignored; will remove after deprecation period of the signature change. - """ - - on = CharmEvents() - - def __init__(self, framework: Framework, key: typing.Optional = None): - super().__init__(framework, None) - - for relation_name in self.framework.meta.relations: - relation_name = relation_name.replace('-', '_') - self.on.define_event(relation_name + '_relation_created', RelationCreatedEvent) - self.on.define_event(relation_name + '_relation_joined', RelationJoinedEvent) - self.on.define_event(relation_name + '_relation_changed', RelationChangedEvent) - self.on.define_event(relation_name + '_relation_departed', RelationDepartedEvent) - self.on.define_event(relation_name + '_relation_broken', RelationBrokenEvent) - - for storage_name in self.framework.meta.storages: - storage_name = storage_name.replace('-', '_') - self.on.define_event(storage_name + '_storage_attached', StorageAttachedEvent) - self.on.define_event(storage_name + '_storage_detaching', StorageDetachingEvent) - - for action_name in self.framework.meta.actions: - action_name = action_name.replace('-', '_') - self.on.define_event(action_name + '_action', ActionEvent) - - @property - def app(self) -> model.Application: - """Application that this unit is part of.""" - return self.framework.model.app - - @property - def unit(self) -> model.Unit: - """Unit that this execution is responsible for.""" - return self.framework.model.unit - - @property - def meta(self) -> 'CharmMeta': - """CharmMeta of this charm. - """ - return self.framework.meta - - @property - def charm_dir(self) -> pathlib.Path: - """Root directory of the Charm as it is running. - """ - return self.framework.charm_dir - - -class CharmMeta: - """Object containing the metadata for the charm. - - This is read from metadata.yaml and/or actions.yaml. Generally charms will - define this information, rather than reading it at runtime. This class is - mostly for the framework to understand what the charm has defined. - - The maintainers, tags, terms, series, and extra_bindings attributes are all - lists of strings. The requires, provides, peers, relations, storage, - resources, and payloads attributes are all mappings of names to instances - of the respective RelationMeta, StorageMeta, ResourceMeta, or PayloadMeta. - - The relations attribute is a convenience accessor which includes all of the - requires, provides, and peers RelationMeta items. If needed, the role of - the relation definition can be obtained from its role attribute. - - Attributes: - name: The name of this charm - summary: Short description of what this charm does - description: Long description for this charm - maintainers: A list of strings of the email addresses of the maintainers - of this charm. - tags: Charm store tag metadata for categories associated with this charm. - terms: Charm store terms that should be agreed to before this charm can - be deployed. (Used for things like licensing issues.) - series: The list of supported OS series that this charm can support. - The first entry in the list is the default series that will be - used by deploy if no other series is requested by the user. - subordinate: True/False whether this charm is intended to be used as a - subordinate charm. - min_juju_version: If supplied, indicates this charm needs features that - are not available in older versions of Juju. - requires: A dict of {name: :class:`RelationMeta` } for each 'requires' relation. - provides: A dict of {name: :class:`RelationMeta` } for each 'provides' relation. - peers: A dict of {name: :class:`RelationMeta` } for each 'peer' relation. - relations: A dict containing all :class:`RelationMeta` attributes (merged from other - sections) - storages: A dict of {name: :class:`StorageMeta`} for each defined storage. - resources: A dict of {name: :class:`ResourceMeta`} for each defined resource. - payloads: A dict of {name: :class:`PayloadMeta`} for each defined payload. - extra_bindings: A dict of additional named bindings that a charm can use - for network configuration. - actions: A dict of {name: :class:`ActionMeta`} for actions that the charm has defined. - Args: - raw: a mapping containing the contents of metadata.yaml - actions_raw: a mapping containing the contents of actions.yaml - """ - - def __init__(self, raw: dict = {}, actions_raw: dict = {}): - self.name = raw.get('name', '') - self.summary = raw.get('summary', '') - self.description = raw.get('description', '') - self.maintainers = [] - if 'maintainer' in raw: - self.maintainers.append(raw['maintainer']) - if 'maintainers' in raw: - self.maintainers.extend(raw['maintainers']) - self.tags = raw.get('tags', []) - self.terms = raw.get('terms', []) - self.series = raw.get('series', []) - self.subordinate = raw.get('subordinate', False) - self.min_juju_version = raw.get('min-juju-version') - self.requires = {name: RelationMeta(RelationRole.requires, name, rel) - for name, rel in raw.get('requires', {}).items()} - self.provides = {name: RelationMeta(RelationRole.provides, name, rel) - for name, rel in raw.get('provides', {}).items()} - self.peers = {name: RelationMeta(RelationRole.peer, name, rel) - for name, rel in raw.get('peers', {}).items()} - self.relations = {} - self.relations.update(self.requires) - self.relations.update(self.provides) - self.relations.update(self.peers) - self.storages = {name: StorageMeta(name, storage) - for name, storage in raw.get('storage', {}).items()} - self.resources = {name: ResourceMeta(name, res) - for name, res in raw.get('resources', {}).items()} - self.payloads = {name: PayloadMeta(name, payload) - for name, payload in raw.get('payloads', {}).items()} - self.extra_bindings = raw.get('extra-bindings', {}) - self.actions = {name: ActionMeta(name, action) for name, action in actions_raw.items()} - - @classmethod - def from_yaml( - cls, metadata: typing.Union[str, typing.TextIO], - actions: typing.Optional[typing.Union[str, typing.TextIO]] = None): - """Instantiate a CharmMeta from a YAML description of metadata.yaml. - - Args: - metadata: A YAML description of charm metadata (name, relations, etc.) - This can be a simple string, or a file-like object. (passed to `yaml.safe_load`). - actions: YAML description of Actions for this charm (eg actions.yaml) - """ - meta = _loadYaml(metadata) - raw_actions = {} - if actions is not None: - raw_actions = _loadYaml(actions) - return cls(meta, raw_actions) - - -class RelationRole(enum.Enum): - peer = 'peer' - requires = 'requires' - provides = 'provides' - - def is_peer(self) -> bool: - """Return whether the current role is peer. - - A convenience to avoid having to import charm. - """ - return self is RelationRole.peer - - -class RelationMeta: - """Object containing metadata about a relation definition. - - Should not be constructed directly by Charm code. Is gotten from one of - :attr:`CharmMeta.peers`, :attr:`CharmMeta.requires`, :attr:`CharmMeta.provides`, - or :attr:`CharmMeta.relations`. - - Attributes: - role: This is one of peer/requires/provides - relation_name: Name of this relation from metadata.yaml - interface_name: Optional definition of the interface protocol. - scope: "global" or "container" scope based on how the relation should be used. - """ - - def __init__(self, role: RelationRole, relation_name: str, raw: dict): - if not isinstance(role, RelationRole): - raise TypeError("role should be a Role, not {!r}".format(role)) - self.role = role - self.relation_name = relation_name - self.interface_name = raw['interface'] - self.scope = raw.get('scope') - - -class StorageMeta: - """Object containing metadata about a storage definition.""" - - def __init__(self, name, raw): - self.storage_name = name - self.type = raw['type'] - self.description = raw.get('description', '') - self.shared = raw.get('shared', False) - self.read_only = raw.get('read-only', False) - self.minimum_size = raw.get('minimum-size') - self.location = raw.get('location') - self.multiple_range = None - if 'multiple' in raw: - range = raw['multiple']['range'] - if '-' not in range: - self.multiple_range = (int(range), int(range)) - else: - range = range.split('-') - self.multiple_range = (int(range[0]), int(range[1]) if range[1] else None) - - -class ResourceMeta: - """Object containing metadata about a resource definition.""" - - def __init__(self, name, raw): - self.resource_name = name - self.type = raw['type'] - self.filename = raw.get('filename', None) - self.description = raw.get('description', '') - - -class PayloadMeta: - """Object containing metadata about a payload definition.""" - - def __init__(self, name, raw): - self.payload_name = name - self.type = raw['type'] - - -class ActionMeta: - """Object containing metadata about an action's definition.""" - - def __init__(self, name, raw=None): - raw = raw or {} - self.name = name - self.title = raw.get('title', '') - self.description = raw.get('description', '') - self.parameters = raw.get('params', {}) # {: } - self.required = raw.get('required', []) # [, ...] diff --git a/coredns/venv/ops/framework.py b/coredns/venv/ops/framework.py deleted file mode 100644 index ad500ca..0000000 --- a/coredns/venv/ops/framework.py +++ /dev/null @@ -1,1073 +0,0 @@ -# Copyright 2020 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import collections -import collections.abc -import inspect -import keyword -import logging -import marshal -import os -import pathlib -import pdb -import re -import sys -import types -import weakref - -from ops import charm -from ops.storage import ( - NoSnapshotError, - SQLiteStorage, -) - -logger = logging.getLogger(__name__) - - -class Handle: - """Handle defines a name for an object in the form of a hierarchical path. - - The provided parent is the object (or that object's handle) that this handle - sits under, or None if the object identified by this handle stands by itself - as the root of its own hierarchy. - - The handle kind is a string that defines a namespace so objects with the - same parent and kind will have unique keys. - - The handle key is a string uniquely identifying the object. No other objects - under the same parent and kind may have the same key. - """ - - def __init__(self, parent, kind, key): - if parent and not isinstance(parent, Handle): - parent = parent.handle - self._parent = parent - self._kind = kind - self._key = key - if parent: - if key: - self._path = "{}/{}[{}]".format(parent, kind, key) - else: - self._path = "{}/{}".format(parent, kind) - else: - if key: - self._path = "{}[{}]".format(kind, key) - else: - self._path = "{}".format(kind) - - def nest(self, kind, key): - return Handle(self, kind, key) - - def __hash__(self): - return hash((self.parent, self.kind, self.key)) - - def __eq__(self, other): - return (self.parent, self.kind, self.key) == (other.parent, other.kind, other.key) - - def __str__(self): - return self.path - - @property - def parent(self): - return self._parent - - @property - def kind(self): - return self._kind - - @property - def key(self): - return self._key - - @property - def path(self): - return self._path - - @classmethod - def from_path(cls, path): - handle = None - for pair in path.split("/"): - pair = pair.split("[") - good = False - if len(pair) == 1: - kind, key = pair[0], None - good = True - elif len(pair) == 2: - kind, key = pair - if key and key[-1] == ']': - key = key[:-1] - good = True - if not good: - raise RuntimeError("attempted to restore invalid handle path {}".format(path)) - handle = Handle(handle, kind, key) - return handle - - -class EventBase: - - def __init__(self, handle): - self.handle = handle - self.deferred = False - - def __repr__(self): - return "<%s via %s>" % (self.__class__.__name__, self.handle) - - def defer(self): - logger.debug("Deferring %s.", self) - self.deferred = True - - def snapshot(self): - """Return the snapshot data that should be persisted. - - Subclasses must override to save any custom state. - """ - return None - - def restore(self, snapshot): - """Restore the value state from the given snapshot. - - Subclasses must override to restore their custom state. - """ - self.deferred = False - - -class EventSource: - """EventSource wraps an event type with a descriptor to facilitate observing and emitting. - - It is generally used as: - - class SomethingHappened(EventBase): - pass - - class SomeObject(Object): - something_happened = EventSource(SomethingHappened) - - With that, instances of that type will offer the someobj.something_happened - attribute which is a BoundEvent and may be used to emit and observe the event. - """ - - def __init__(self, event_type): - if not isinstance(event_type, type) or not issubclass(event_type, EventBase): - raise RuntimeError( - 'Event requires a subclass of EventBase as an argument, got {}'.format(event_type)) - self.event_type = event_type - self.event_kind = None - self.emitter_type = None - - def _set_name(self, emitter_type, event_kind): - if self.event_kind is not None: - raise RuntimeError( - 'EventSource({}) reused as {}.{} and {}.{}'.format( - self.event_type.__name__, - self.emitter_type.__name__, - self.event_kind, - emitter_type.__name__, - event_kind, - )) - self.event_kind = event_kind - self.emitter_type = emitter_type - - def __get__(self, emitter, emitter_type=None): - if emitter is None: - return self - # Framework might not be available if accessed as CharmClass.on.event - # rather than charm_instance.on.event, but in that case it couldn't be - # emitted anyway, so there's no point to registering it. - framework = getattr(emitter, 'framework', None) - if framework is not None: - framework.register_type(self.event_type, emitter, self.event_kind) - return BoundEvent(emitter, self.event_type, self.event_kind) - - -class BoundEvent: - - def __repr__(self): - return ''.format( - self.event_type.__name__, - type(self.emitter).__name__, - self.event_kind, - hex(id(self)), - ) - - def __init__(self, emitter, event_type, event_kind): - self.emitter = emitter - self.event_type = event_type - self.event_kind = event_kind - - def emit(self, *args, **kwargs): - """Emit event to all registered observers. - - The current storage state is committed before and after each observer is notified. - """ - framework = self.emitter.framework - key = framework._next_event_key() - event = self.event_type(Handle(self.emitter, self.event_kind, key), *args, **kwargs) - framework._emit(event) - - -class HandleKind: - """Helper descriptor to define the Object.handle_kind field. - - The handle_kind for an object defaults to its type name, but it may - be explicitly overridden if desired. - """ - - def __get__(self, obj, obj_type): - kind = obj_type.__dict__.get("handle_kind") - if kind: - return kind - return obj_type.__name__ - - -class _Metaclass(type): - """Helper class to ensure proper instantiation of Object-derived classes. - - This class currently has a single purpose: events derived from EventSource - that are class attributes of Object-derived classes need to be told what - their name is in that class. For example, in - - class SomeObject(Object): - something_happened = EventSource(SomethingHappened) - - the instance of EventSource needs to know it's called 'something_happened'. - - Starting from python 3.6 we could use __set_name__ on EventSource for this, - but until then this (meta)class does the equivalent work. - - TODO: when we drop support for 3.5 drop this class, and rename _set_name in - EventSource to __set_name__; everything should continue to work. - - """ - - def __new__(typ, *a, **kw): - k = super().__new__(typ, *a, **kw) - # k is now the Object-derived class; loop over its class attributes - for n, v in vars(k).items(): - # we could do duck typing here if we want to support - # non-EventSource-derived shenanigans. We don't. - if isinstance(v, EventSource): - # this is what 3.6+ does automatically for us: - v._set_name(k, n) - return k - - -class Object(metaclass=_Metaclass): - - handle_kind = HandleKind() - - def __init__(self, parent, key): - kind = self.handle_kind - if isinstance(parent, Framework): - self.framework = parent - # Avoid Framework instances having a circular reference to themselves. - if self.framework is self: - self.framework = weakref.proxy(self.framework) - self.handle = Handle(None, kind, key) - else: - self.framework = parent.framework - self.handle = Handle(parent, kind, key) - self.framework._track(self) - - # TODO Detect conflicting handles here. - - @property - def model(self): - return self.framework.model - - -class ObjectEvents(Object): - """Convenience type to allow defining .on attributes at class level.""" - - handle_kind = "on" - - def __init__(self, parent=None, key=None): - if parent is not None: - super().__init__(parent, key) - else: - self._cache = weakref.WeakKeyDictionary() - - def __get__(self, emitter, emitter_type): - if emitter is None: - return self - instance = self._cache.get(emitter) - if instance is None: - # Same type, different instance, more data. Doing this unusual construct - # means people can subclass just this one class to have their own 'on'. - instance = self._cache[emitter] = type(self)(emitter) - return instance - - @classmethod - def define_event(cls, event_kind, event_type): - """Define an event on this type at runtime. - - cls: a type to define an event on. - - event_kind: an attribute name that will be used to access the - event. Must be a valid python identifier, not be a keyword - or an existing attribute. - - event_type: a type of the event to define. - - """ - prefix = 'unable to define an event with event_kind that ' - if not event_kind.isidentifier(): - raise RuntimeError(prefix + 'is not a valid python identifier: ' + event_kind) - elif keyword.iskeyword(event_kind): - raise RuntimeError(prefix + 'is a python keyword: ' + event_kind) - try: - getattr(cls, event_kind) - raise RuntimeError( - prefix + 'overlaps with an existing type {} attribute: {}'.format(cls, event_kind)) - except AttributeError: - pass - - event_descriptor = EventSource(event_type) - event_descriptor._set_name(cls, event_kind) - setattr(cls, event_kind, event_descriptor) - - def events(self): - """Return a mapping of event_kinds to bound_events for all available events. - """ - events_map = {} - # We have to iterate over the class rather than instance to allow for properties which - # might call this method (e.g., event views), leading to infinite recursion. - for attr_name, attr_value in inspect.getmembers(type(self)): - if isinstance(attr_value, EventSource): - # We actually care about the bound_event, however, since it - # provides the most info for users of this method. - event_kind = attr_name - bound_event = getattr(self, event_kind) - events_map[event_kind] = bound_event - return events_map - - def __getitem__(self, key): - return PrefixedEvents(self, key) - - -class PrefixedEvents: - - def __init__(self, emitter, key): - self._emitter = emitter - self._prefix = key.replace("-", "_") + '_' - - def __getattr__(self, name): - return getattr(self._emitter, self._prefix + name) - - -class PreCommitEvent(EventBase): - pass - - -class CommitEvent(EventBase): - pass - - -class FrameworkEvents(ObjectEvents): - pre_commit = EventSource(PreCommitEvent) - commit = EventSource(CommitEvent) - - -class NoTypeError(Exception): - - def __init__(self, handle_path): - self.handle_path = handle_path - - def __str__(self): - return "cannot restore {} since no class was registered for it".format(self.handle_path) - - -# the message to show to the user when a pdb breakpoint goes active -_BREAKPOINT_WELCOME_MESSAGE = """ -Starting pdb to debug charm operator. -Run `h` for help, `c` to continue, or `exit`/CTRL-d to abort. -Future breakpoints may interrupt execution again. -More details at https://discourse.jujucharms.com/t/debugging-charm-hooks - -""" - - -_event_regex = r'^(|.*/)on/[a-zA-Z_]+\[\d+\]$' - - -class Framework(Object): - - on = FrameworkEvents() - - # Override properties from Object so that we can set them in __init__. - model = None - meta = None - charm_dir = None - - def __init__(self, storage, charm_dir, meta, model): - - super().__init__(self, None) - - self.charm_dir = charm_dir - self.meta = meta - self.model = model - self._observers = [] # [(observer_path, method_name, parent_path, event_key)] - self._observer = weakref.WeakValueDictionary() # {observer_path: observer} - self._objects = weakref.WeakValueDictionary() - self._type_registry = {} # {(parent_path, kind): cls} - self._type_known = set() # {cls} - - if isinstance(storage, (str, pathlib.Path)): - logger.warning( - "deprecated: Framework now takes a Storage not a path") - storage = SQLiteStorage(storage) - self._storage = storage - - # We can't use the higher-level StoredState because it relies on events. - self.register_type(StoredStateData, None, StoredStateData.handle_kind) - stored_handle = Handle(None, StoredStateData.handle_kind, '_stored') - try: - self._stored = self.load_snapshot(stored_handle) - except NoSnapshotError: - self._stored = StoredStateData(self, '_stored') - self._stored['event_count'] = 0 - - # Hook into builtin breakpoint, so if Python >= 3.7, devs will be able to just do - # breakpoint(); if Python < 3.7, this doesn't affect anything - sys.breakpointhook = self.breakpoint - - # Flag to indicate that we already presented the welcome message in a debugger breakpoint - self._breakpoint_welcomed = False - - # Parse once the env var, which may be used multiple times later - debug_at = os.environ.get('JUJU_DEBUG_AT') - self._juju_debug_at = debug_at.split(',') if debug_at else () - - def close(self): - self._storage.close() - - def _track(self, obj): - """Track object and ensure it is the only object created using its handle path.""" - if obj is self: - # Framework objects don't track themselves - return - if obj.handle.path in self.framework._objects: - raise RuntimeError( - 'two objects claiming to be {} have been created'.format(obj.handle.path)) - self._objects[obj.handle.path] = obj - - def _forget(self, obj): - """Stop tracking the given object. See also _track.""" - self._objects.pop(obj.handle.path, None) - - def commit(self): - # Give a chance for objects to persist data they want to before a commit is made. - self.on.pre_commit.emit() - # Make sure snapshots are saved by instances of StoredStateData. Any possible state - # modifications in on_commit handlers of instances of other classes will not be persisted. - self.on.commit.emit() - # Save our event count after all events have been emitted. - self.save_snapshot(self._stored) - self._storage.commit() - - def register_type(self, cls, parent, kind=None): - if parent and not isinstance(parent, Handle): - parent = parent.handle - if parent: - parent_path = parent.path - else: - parent_path = None - if not kind: - kind = cls.handle_kind - self._type_registry[(parent_path, kind)] = cls - self._type_known.add(cls) - - def save_snapshot(self, value): - """Save a persistent snapshot of the provided value. - - The provided value must implement the following interface: - - value.handle = Handle(...) - value.snapshot() => {...} # Simple builtin types only. - value.restore(snapshot) # Restore custom state from prior snapshot. - """ - if type(value) not in self._type_known: - raise RuntimeError( - 'cannot save {} values before registering that type'.format(type(value).__name__)) - data = value.snapshot() - - # Use marshal as a validator, enforcing the use of simple types, as we later the - # information is really pickled, which is too error prone for future evolution of the - # stored data (e.g. if the developer stores a custom object and later changes its - # class name; when unpickling the original class will not be there and event - # data loading will fail). - try: - marshal.dumps(data) - except ValueError: - msg = "unable to save the data for {}, it must contain only simple types: {!r}" - raise ValueError(msg.format(value.__class__.__name__, data)) - - self._storage.save_snapshot(value.handle.path, data) - - def load_snapshot(self, handle): - parent_path = None - if handle.parent: - parent_path = handle.parent.path - cls = self._type_registry.get((parent_path, handle.kind)) - if not cls: - raise NoTypeError(handle.path) - data = self._storage.load_snapshot(handle.path) - obj = cls.__new__(cls) - obj.framework = self - obj.handle = handle - obj.restore(data) - self._track(obj) - return obj - - def drop_snapshot(self, handle): - self._storage.drop_snapshot(handle.path) - - def observe(self, bound_event: BoundEvent, observer: types.MethodType): - """Register observer to be called when bound_event is emitted. - - The bound_event is generally provided as an attribute of the object that emits - the event, and is created in this style: - - class SomeObject: - something_happened = Event(SomethingHappened) - - That event may be observed as: - - framework.observe(someobj.something_happened, self._on_something_happened) - - Raises: - RuntimeError: if bound_event or observer are the wrong type. - """ - if not isinstance(bound_event, BoundEvent): - raise RuntimeError( - 'Framework.observe requires a BoundEvent as second parameter, got {}'.format( - bound_event)) - if not isinstance(observer, types.MethodType): - # help users of older versions of the framework - if isinstance(observer, charm.CharmBase): - raise TypeError( - 'observer methods must now be explicitly provided;' - ' please replace observe(self.on.{0}, self)' - ' with e.g. observe(self.on.{0}, self._on_{0})'.format( - bound_event.event_kind)) - raise RuntimeError( - 'Framework.observe requires a method as third parameter, got {}'.format(observer)) - - event_type = bound_event.event_type - event_kind = bound_event.event_kind - emitter = bound_event.emitter - - self.register_type(event_type, emitter, event_kind) - - if hasattr(emitter, "handle"): - emitter_path = emitter.handle.path - else: - raise RuntimeError( - 'event emitter {} must have a "handle" attribute'.format(type(emitter).__name__)) - - # Validate that the method has an acceptable call signature. - sig = inspect.signature(observer) - # Self isn't included in the params list, so the first arg will be the event. - extra_params = list(sig.parameters.values())[1:] - - method_name = observer.__name__ - observer = observer.__self__ - if not sig.parameters: - raise TypeError( - '{}.{} must accept event parameter'.format(type(observer).__name__, method_name)) - elif any(param.default is inspect.Parameter.empty for param in extra_params): - # Allow for additional optional params, since there's no reason to exclude them, but - # required params will break. - raise TypeError( - '{}.{} has extra required parameter'.format(type(observer).__name__, method_name)) - - # TODO Prevent the exact same parameters from being registered more than once. - - self._observer[observer.handle.path] = observer - self._observers.append((observer.handle.path, method_name, emitter_path, event_kind)) - - def _next_event_key(self): - """Return the next event key that should be used, incrementing the internal counter.""" - # Increment the count first; this means the keys will start at 1, and 0 - # means no events have been emitted. - self._stored['event_count'] += 1 - return str(self._stored['event_count']) - - def _emit(self, event): - """See BoundEvent.emit for the public way to call this.""" - - saved = False - event_path = event.handle.path - event_kind = event.handle.kind - parent_path = event.handle.parent.path - # TODO Track observers by (parent_path, event_kind) rather than as a list of - # all observers. Avoiding linear search through all observers for every event - for observer_path, method_name, _parent_path, _event_kind in self._observers: - if _parent_path != parent_path: - continue - if _event_kind and _event_kind != event_kind: - continue - if not saved: - # Save the event for all known observers before the first notification - # takes place, so that either everyone interested sees it, or nobody does. - self.save_snapshot(event) - saved = True - # Again, only commit this after all notices are saved. - self._storage.save_notice(event_path, observer_path, method_name) - if saved: - self._reemit(event_path) - - def reemit(self): - """Reemit previously deferred events to the observers that deferred them. - - Only the specific observers that have previously deferred the event will be - notified again. Observers that asked to be notified about events after it's - been first emitted won't be notified, as that would mean potentially observing - events out of order. - """ - self._reemit() - - def _reemit(self, single_event_path=None): - last_event_path = None - deferred = True - for event_path, observer_path, method_name in self._storage.notices(single_event_path): - event_handle = Handle.from_path(event_path) - - if last_event_path != event_path: - if not deferred and last_event_path is not None: - self._storage.drop_snapshot(last_event_path) - last_event_path = event_path - deferred = False - - try: - event = self.load_snapshot(event_handle) - except NoTypeError: - self._storage.drop_notice(event_path, observer_path, method_name) - continue - - event.deferred = False - observer = self._observer.get(observer_path) - if observer: - if single_event_path is None: - logger.debug("Re-emitting %s.", event) - custom_handler = getattr(observer, method_name, None) - if custom_handler: - event_is_from_juju = isinstance(event, charm.HookEvent) - event_is_action = isinstance(event, charm.ActionEvent) - if (event_is_from_juju or event_is_action) and 'hook' in self._juju_debug_at: - # Present the welcome message and run under PDB. - self._show_debug_code_message() - pdb.runcall(custom_handler, event) - else: - # Regular call to the registered method. - custom_handler(event) - - if event.deferred: - deferred = True - else: - self._storage.drop_notice(event_path, observer_path, method_name) - # We intentionally consider this event to be dead and reload it from - # scratch in the next path. - self.framework._forget(event) - - if not deferred and last_event_path is not None: - self._storage.drop_snapshot(last_event_path) - - def _show_debug_code_message(self): - """Present the welcome message (only once!) when using debugger functionality.""" - if not self._breakpoint_welcomed: - self._breakpoint_welcomed = True - print(_BREAKPOINT_WELCOME_MESSAGE, file=sys.stderr, end='') - - def breakpoint(self, name=None): - """Add breakpoint, optionally named, at the place where this method is called. - - For the breakpoint to be activated the JUJU_DEBUG_AT environment variable - must be set to "all" or to the specific name parameter provided, if any. In every - other situation calling this method does nothing. - - The framework also provides a standard breakpoint named "hook", that will - stop execution when a hook event is about to be handled. - - For those reasons, the "all" and "hook" breakpoint names are reserved. - """ - # If given, validate the name comply with all the rules - if name is not None: - if not isinstance(name, str): - raise TypeError('breakpoint names must be strings') - if name in ('hook', 'all'): - raise ValueError('breakpoint names "all" and "hook" are reserved') - if not re.match(r'^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$', name): - raise ValueError('breakpoint names must look like "foo" or "foo-bar"') - - indicated_breakpoints = self._juju_debug_at - if not indicated_breakpoints: - return - - if 'all' in indicated_breakpoints or name in indicated_breakpoints: - self._show_debug_code_message() - - # If we call set_trace() directly it will open the debugger *here*, so indicating - # it to use our caller's frame - code_frame = inspect.currentframe().f_back - pdb.Pdb().set_trace(code_frame) - else: - logger.warning( - "Breakpoint %r skipped (not found in the requested breakpoints: %s)", - name, indicated_breakpoints) - - def remove_unreferenced_events(self): - """Remove events from storage that are not referenced. - - In older versions of the framework, events that had no observers would get recorded but - never deleted. This makes a best effort to find these events and remove them from the - database. - """ - event_regex = re.compile(_event_regex) - to_remove = [] - for handle_path in self._storage.list_snapshots(): - if event_regex.match(handle_path): - notices = self._storage.notices(handle_path) - if next(notices, None) is None: - # There are no notices for this handle_path, it is valid to remove it - to_remove.append(handle_path) - for handle_path in to_remove: - self._storage.drop_snapshot(handle_path) - - -class StoredStateData(Object): - - def __init__(self, parent, attr_name): - super().__init__(parent, attr_name) - self._cache = {} - self.dirty = False - - def __getitem__(self, key): - return self._cache.get(key) - - def __setitem__(self, key, value): - self._cache[key] = value - self.dirty = True - - def __contains__(self, key): - return key in self._cache - - def snapshot(self): - return self._cache - - def restore(self, snapshot): - self._cache = snapshot - self.dirty = False - - def on_commit(self, event): - if self.dirty: - self.framework.save_snapshot(self) - self.dirty = False - - -class BoundStoredState: - - def __init__(self, parent, attr_name): - parent.framework.register_type(StoredStateData, parent) - - handle = Handle(parent, StoredStateData.handle_kind, attr_name) - try: - data = parent.framework.load_snapshot(handle) - except NoSnapshotError: - data = StoredStateData(parent, attr_name) - - # __dict__ is used to avoid infinite recursion. - self.__dict__["_data"] = data - self.__dict__["_attr_name"] = attr_name - - parent.framework.observe(parent.framework.on.commit, self._data.on_commit) - - def __getattr__(self, key): - # "on" is the only reserved key that can't be used in the data map. - if key == "on": - return self._data.on - if key not in self._data: - raise AttributeError("attribute '{}' is not stored".format(key)) - return _wrap_stored(self._data, self._data[key]) - - def __setattr__(self, key, value): - if key == "on": - raise AttributeError("attribute 'on' is reserved and cannot be set") - - value = _unwrap_stored(self._data, value) - - if not isinstance(value, (type(None), int, float, str, bytes, list, dict, set)): - raise AttributeError( - 'attribute {!r} cannot be a {}: must be int/float/dict/list/etc'.format( - key, type(value).__name__)) - - self._data[key] = _unwrap_stored(self._data, value) - - def set_default(self, **kwargs): - """"Set the value of any given key if it has not already been set""" - for k, v in kwargs.items(): - if k not in self._data: - self._data[k] = v - - -class StoredState: - """A class used to store data the charm needs persisted across invocations. - - Example:: - - class MyClass(Object): - _stored = StoredState() - - Instances of `MyClass` can transparently save state between invocations by - setting attributes on `_stored`. Initial state should be set with - `set_default` on the bound object, that is:: - - class MyClass(Object): - _stored = StoredState() - - def __init__(self, parent, key): - super().__init__(parent, key) - self._stored.set_default(seen=set()) - self.framework.observe(self.on.seen, self._on_seen) - - def _on_seen(self, event): - self._stored.seen.add(event.uuid) - - """ - - def __init__(self): - self.parent_type = None - self.attr_name = None - - def __get__(self, parent, parent_type=None): - if self.parent_type is not None and self.parent_type not in parent_type.mro(): - # the StoredState instance is being shared between two unrelated classes - # -> unclear what is exepcted of us -> bail out - raise RuntimeError( - 'StoredState shared by {} and {}'.format( - self.parent_type.__name__, parent_type.__name__)) - - if parent is None: - # accessing via the class directly (e.g. MyClass.stored) - return self - - bound = None - if self.attr_name is not None: - bound = parent.__dict__.get(self.attr_name) - if bound is not None: - # we already have the thing from a previous pass, huzzah - return bound - - # need to find ourselves amongst the parent's bases - for cls in parent_type.mro(): - for attr_name, attr_value in cls.__dict__.items(): - if attr_value is not self: - continue - # we've found ourselves! is it the first time? - if bound is not None: - # the StoredState instance is being stored in two different - # attributes -> unclear what is expected of us -> bail out - raise RuntimeError("StoredState shared by {0}.{1} and {0}.{2}".format( - cls.__name__, self.attr_name, attr_name)) - # we've found ourselves for the first time; save where, and bind the object - self.attr_name = attr_name - self.parent_type = cls - bound = BoundStoredState(parent, attr_name) - - if bound is not None: - # cache the bound object to avoid the expensive lookup the next time - # (don't use setattr, to keep things symmetric with the fast-path lookup above) - parent.__dict__[self.attr_name] = bound - return bound - - raise AttributeError( - 'cannot find {} attribute in type {}'.format( - self.__class__.__name__, parent_type.__name__)) - - -def _wrap_stored(parent_data, value): - t = type(value) - if t is dict: - return StoredDict(parent_data, value) - if t is list: - return StoredList(parent_data, value) - if t is set: - return StoredSet(parent_data, value) - return value - - -def _unwrap_stored(parent_data, value): - t = type(value) - if t is StoredDict or t is StoredList or t is StoredSet: - return value._under - return value - - -class StoredDict(collections.abc.MutableMapping): - - def __init__(self, stored_data, under): - self._stored_data = stored_data - self._under = under - - def __getitem__(self, key): - return _wrap_stored(self._stored_data, self._under[key]) - - def __setitem__(self, key, value): - self._under[key] = _unwrap_stored(self._stored_data, value) - self._stored_data.dirty = True - - def __delitem__(self, key): - del self._under[key] - self._stored_data.dirty = True - - def __iter__(self): - return self._under.__iter__() - - def __len__(self): - return len(self._under) - - def __eq__(self, other): - if isinstance(other, StoredDict): - return self._under == other._under - elif isinstance(other, collections.abc.Mapping): - return self._under == other - else: - return NotImplemented - - -class StoredList(collections.abc.MutableSequence): - - def __init__(self, stored_data, under): - self._stored_data = stored_data - self._under = under - - def __getitem__(self, index): - return _wrap_stored(self._stored_data, self._under[index]) - - def __setitem__(self, index, value): - self._under[index] = _unwrap_stored(self._stored_data, value) - self._stored_data.dirty = True - - def __delitem__(self, index): - del self._under[index] - self._stored_data.dirty = True - - def __len__(self): - return len(self._under) - - def insert(self, index, value): - self._under.insert(index, value) - self._stored_data.dirty = True - - def append(self, value): - self._under.append(value) - self._stored_data.dirty = True - - def __eq__(self, other): - if isinstance(other, StoredList): - return self._under == other._under - elif isinstance(other, collections.abc.Sequence): - return self._under == other - else: - return NotImplemented - - def __lt__(self, other): - if isinstance(other, StoredList): - return self._under < other._under - elif isinstance(other, collections.abc.Sequence): - return self._under < other - else: - return NotImplemented - - def __le__(self, other): - if isinstance(other, StoredList): - return self._under <= other._under - elif isinstance(other, collections.abc.Sequence): - return self._under <= other - else: - return NotImplemented - - def __gt__(self, other): - if isinstance(other, StoredList): - return self._under > other._under - elif isinstance(other, collections.abc.Sequence): - return self._under > other - else: - return NotImplemented - - def __ge__(self, other): - if isinstance(other, StoredList): - return self._under >= other._under - elif isinstance(other, collections.abc.Sequence): - return self._under >= other - else: - return NotImplemented - - -class StoredSet(collections.abc.MutableSet): - - def __init__(self, stored_data, under): - self._stored_data = stored_data - self._under = under - - def add(self, key): - self._under.add(key) - self._stored_data.dirty = True - - def discard(self, key): - self._under.discard(key) - self._stored_data.dirty = True - - def __contains__(self, key): - return key in self._under - - def __iter__(self): - return self._under.__iter__() - - def __len__(self): - return len(self._under) - - @classmethod - def _from_iterable(cls, it): - """Construct an instance of the class from any iterable input. - - Per https://docs.python.org/3/library/collections.abc.html - if the Set mixin is being used in a class with a different constructor signature, - you will need to override _from_iterable() with a classmethod that can construct - new instances from an iterable argument. - """ - return set(it) - - def __le__(self, other): - if isinstance(other, StoredSet): - return self._under <= other._under - elif isinstance(other, collections.abc.Set): - return self._under <= other - else: - return NotImplemented - - def __ge__(self, other): - if isinstance(other, StoredSet): - return self._under >= other._under - elif isinstance(other, collections.abc.Set): - return self._under >= other - else: - return NotImplemented - - def __eq__(self, other): - if isinstance(other, StoredSet): - return self._under == other._under - elif isinstance(other, collections.abc.Set): - return self._under == other - else: - return NotImplemented diff --git a/coredns/venv/ops/jujuversion.py b/coredns/venv/ops/jujuversion.py deleted file mode 100644 index 9837c50..0000000 --- a/coredns/venv/ops/jujuversion.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2020 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import re -from functools import total_ordering - - -@total_ordering -class JujuVersion: - - PATTERN = r'''^ - (?P\d{1,9})\.(?P\d{1,9}) # and numbers are always there - ((?:\.|-(?P[a-z]+))(?P\d{1,9}))? # sometimes with . or - - (\.(?P\d{1,9}))?$ # and sometimes with a number. - ''' - - def __init__(self, version): - m = re.match(self.PATTERN, version, re.VERBOSE) - if not m: - raise RuntimeError('"{}" is not a valid Juju version string'.format(version)) - - d = m.groupdict() - self.major = int(m.group('major')) - self.minor = int(m.group('minor')) - self.tag = d['tag'] or '' - self.patch = int(d['patch'] or 0) - self.build = int(d['build'] or 0) - - def __repr__(self): - if self.tag: - s = '{}.{}-{}{}'.format(self.major, self.minor, self.tag, self.patch) - else: - s = '{}.{}.{}'.format(self.major, self.minor, self.patch) - if self.build > 0: - s += '.{}'.format(self.build) - return s - - def __eq__(self, other): - if self is other: - return True - if isinstance(other, str): - other = type(self)(other) - elif not isinstance(other, JujuVersion): - raise RuntimeError('cannot compare Juju version "{}" with "{}"'.format(self, other)) - return ( - self.major == other.major - and self.minor == other.minor - and self.tag == other.tag - and self.build == other.build - and self.patch == other.patch) - - def __lt__(self, other): - if self is other: - return False - if isinstance(other, str): - other = type(self)(other) - elif not isinstance(other, JujuVersion): - raise RuntimeError('cannot compare Juju version "{}" with "{}"'.format(self, other)) - - if self.major != other.major: - return self.major < other.major - elif self.minor != other.minor: - return self.minor < other.minor - elif self.tag != other.tag: - if not self.tag: - return False - elif not other.tag: - return True - return self.tag < other.tag - elif self.patch != other.patch: - return self.patch < other.patch - elif self.build != other.build: - return self.build < other.build - return False - - @classmethod - def from_environ(cls) -> 'JujuVersion': - """Build a JujuVersion from JUJU_VERSION.""" - v = os.environ.get('JUJU_VERSION') - if v is None: - v = '0.0.0' - return cls(v) - - def has_app_data(self) -> bool: - """Determine whether this juju version knows about app data.""" - return (self.major, self.minor, self.patch) >= (2, 7, 0) - - def is_dispatch_aware(self) -> bool: - """Determine whether this juju version knows about dispatch.""" - return (self.major, self.minor, self.patch) >= (2, 8, 0) - - def has_controller_storage(self) -> bool: - """Determine whether this juju version supports controller-side storage.""" - return (self.major, self.minor, self.patch) >= (2, 8, 0) diff --git a/coredns/venv/ops/lib/__init__.py b/coredns/venv/ops/lib/__init__.py deleted file mode 100644 index 98c0cd1..0000000 --- a/coredns/venv/ops/lib/__init__.py +++ /dev/null @@ -1,262 +0,0 @@ -# Copyright 2020 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import logging -import os -import re -import sys - -from ast import literal_eval -from importlib.util import module_from_spec -from importlib.machinery import ModuleSpec -from pkgutil import get_importer -from types import ModuleType -from typing import List - -__all__ = ('use', 'autoimport') - -logger = logging.getLogger(__name__) - -_libraries = None - -_libline_re = re.compile(r'''^LIB([A-Z]+)\s*=\s*([0-9]+|['"][a-zA-Z0-9_.\-@]+['"])''') -_libname_re = re.compile(r'''^[a-z][a-z0-9]+$''') - -# Not perfect, but should do for now. -_libauthor_re = re.compile(r'''^[A-Za-z0-9_+.-]+@[a-z0-9_-]+(?:\.[a-z0-9_-]+)*\.[a-z]{2,3}$''') - - -def use(name: str, api: int, author: str) -> ModuleType: - """Use a library from the ops libraries. - - Args: - name: the name of the library requested. - api: the API version of the library. - author: the author of the library. If not given, requests the - one in the standard library. - Raises: - ImportError: if the library cannot be found. - TypeError: if the name, api, or author are the wrong type. - ValueError: if the name, api, or author are invalid. - """ - if not isinstance(name, str): - raise TypeError("invalid library name: {!r} (must be a str)".format(name)) - if not isinstance(author, str): - raise TypeError("invalid library author: {!r} (must be a str)".format(author)) - if not isinstance(api, int): - raise TypeError("invalid library API: {!r} (must be an int)".format(api)) - if api < 0: - raise ValueError('invalid library api: {} (must be ≥0)'.format(api)) - if not _libname_re.match(name): - raise ValueError("invalid library name: {!r} (chars and digits only)".format(name)) - if not _libauthor_re.match(author): - raise ValueError("invalid library author email: {!r}".format(author)) - - if _libraries is None: - autoimport() - - versions = _libraries.get((name, author), ()) - for lib in versions: - if lib.api == api: - return lib.import_module() - - others = ', '.join(str(lib.api) for lib in versions) - if others: - msg = 'cannot find "{}" from "{}" with API version {} (have {})'.format( - name, author, api, others) - else: - msg = 'cannot find library "{}" from "{}"'.format(name, author) - - raise ImportError(msg, name=name) - - -def autoimport(): - """Find all libs in the path and enable use of them. - - You only need to call this if you've installed a package or - otherwise changed sys.path in the current run, and need to see the - changes. Otherwise libraries are found on first call of `use`. - """ - global _libraries - _libraries = {} - for spec in _find_all_specs(sys.path): - lib = _parse_lib(spec) - if lib is None: - continue - - versions = _libraries.setdefault((lib.name, lib.author), []) - versions.append(lib) - versions.sort(reverse=True) - - -def _find_all_specs(path): - for sys_dir in path: - if sys_dir == "": - sys_dir = "." - try: - top_dirs = os.listdir(sys_dir) - except (FileNotFoundError, NotADirectoryError): - continue - except OSError as e: - logger.debug("Tried to look for ops.lib packages under '%s': %s", sys_dir, e) - continue - logger.debug("Looking for ops.lib packages under '%s'", sys_dir) - for top_dir in top_dirs: - opslib = os.path.join(sys_dir, top_dir, 'opslib') - try: - lib_dirs = os.listdir(opslib) - except (FileNotFoundError, NotADirectoryError): - continue - except OSError as e: - logger.debug(" Tried '%s': %s", opslib, e) # *lots* of things checked here - continue - else: - logger.debug(" Trying '%s'", opslib) - finder = get_importer(opslib) - if finder is None: - logger.debug(" Finder for '%s' is None", opslib) - continue - if not hasattr(finder, 'find_spec'): - logger.debug(" Finder for '%s' has no find_spec", opslib) - continue - for lib_dir in lib_dirs: - spec_name = "{}.opslib.{}".format(top_dir, lib_dir) - spec = finder.find_spec(spec_name) - if spec is None: - logger.debug(" No spec for %r", spec_name) - continue - if spec.loader is None: - # a namespace package; not supported - logger.debug(" No loader for %r (probably a namespace package)", spec_name) - continue - - logger.debug(" Found %r", spec_name) - yield spec - - -# only the first this many lines of a file are looked at for the LIB* constants -_MAX_LIB_LINES = 99 -# these keys, with these types, are needed to have an opslib -_NEEDED_KEYS = {'NAME': str, 'AUTHOR': str, 'API': int, 'PATCH': int} - - -def _join_and(keys: List[str]) -> str: - if len(keys) == 0: - return "" - if len(keys) == 1: - return keys[0] - return ", ".join(keys[:-1]) + ", and " + keys[-1] - - -class _Missing: - """A silly little helper to only work out the difference between - what was found and what was needed when logging""" - - def __init__(self, found): - self._found = found - - def __str__(self): - exp = set(_NEEDED_KEYS) - got = set(self._found) - if len(got) == 0: - return "missing {}".format(_join_and(sorted(exp))) - return "got {}, but missing {}".format( - _join_and(sorted(got)), - _join_and(sorted(exp - got))) - - -def _parse_lib(spec): - if spec.origin is None: - # "can't happen" - logger.warning("No origin for %r (no idea why; please report)", spec.name) - return None - - logger.debug(" Parsing %r", spec.name) - - try: - with open(spec.origin, 'rt', encoding='utf-8') as f: - libinfo = {} - for n, line in enumerate(f): - if len(libinfo) == len(_NEEDED_KEYS): - break - if n > _MAX_LIB_LINES: - logger.debug( - " Missing opslib metadata after reading to line %d: %s", - _MAX_LIB_LINES, _Missing(libinfo)) - return None - m = _libline_re.match(line) - if m is None: - continue - key, value = m.groups() - if key in _NEEDED_KEYS: - value = literal_eval(value) - if not isinstance(value, _NEEDED_KEYS[key]): - logger.debug( - " Bad type for %s: expected %s, got %s", - key, _NEEDED_KEYS[key].__name__, type(value).__name__) - return None - libinfo[key] = value - else: - if len(libinfo) != len(_NEEDED_KEYS): - logger.debug( - " Missing opslib metadata after reading to end of file: %s", - _Missing(libinfo)) - return None - except Exception as e: - logger.debug(" Failed: %s", e) - return None - - lib = _Lib(spec, libinfo['NAME'], libinfo['AUTHOR'], libinfo['API'], libinfo['PATCH']) - logger.debug(" Success: found library %s", lib) - - return lib - - -class _Lib: - - def __init__(self, spec: ModuleSpec, name: str, author: str, api: int, patch: int): - self.spec = spec - self.name = name - self.author = author - self.api = api - self.patch = patch - - self._module = None - - def __repr__(self): - return "<_Lib {}>".format(self) - - def __str__(self): - return "{0.name} by {0.author}, API {0.api}, patch {0.patch}".format(self) - - def import_module(self) -> ModuleType: - if self._module is None: - module = module_from_spec(self.spec) - self.spec.loader.exec_module(module) - self._module = module - return self._module - - def __eq__(self, other): - if not isinstance(other, _Lib): - return NotImplemented - a = (self.name, self.author, self.api, self.patch) - b = (other.name, other.author, other.api, other.patch) - return a == b - - def __lt__(self, other): - if not isinstance(other, _Lib): - return NotImplemented - a = (self.name, self.author, self.api, self.patch) - b = (other.name, other.author, other.api, other.patch) - return a < b diff --git a/coredns/venv/ops/lib/__pycache__/__init__.cpython-38.pyc b/coredns/venv/ops/lib/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index f06f4b5..0000000 Binary files a/coredns/venv/ops/lib/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/ops/log.py b/coredns/venv/ops/log.py deleted file mode 100644 index 4aac554..0000000 --- a/coredns/venv/ops/log.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2020 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import sys -import logging - - -class JujuLogHandler(logging.Handler): - """A handler for sending logs to Juju via juju-log.""" - - def __init__(self, model_backend, level=logging.DEBUG): - super().__init__(level) - self.model_backend = model_backend - - def emit(self, record): - self.model_backend.juju_log(record.levelname, self.format(record)) - - -def setup_root_logging(model_backend, debug=False): - """Setup python logging to forward messages to juju-log. - - By default, logging is set to DEBUG level, and messages will be filtered by Juju. - Charmers can also set their own default log level with:: - - logging.getLogger().setLevel(logging.INFO) - - model_backend -- a ModelBackend to use for juju-log - debug -- if True, write logs to stderr as well as to juju-log. - """ - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - logger.addHandler(JujuLogHandler(model_backend)) - if debug: - handler = logging.StreamHandler() - formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') - handler.setFormatter(formatter) - logger.addHandler(handler) - - sys.excepthook = lambda etype, value, tb: logger.error( - "Uncaught exception while in charm code:", exc_info=(etype, value, tb)) diff --git a/coredns/venv/ops/main.py b/coredns/venv/ops/main.py deleted file mode 100644 index 3e1ea94..0000000 --- a/coredns/venv/ops/main.py +++ /dev/null @@ -1,404 +0,0 @@ -# Copyright 2019-2020 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import inspect -import logging -import os -import shutil -import subprocess -import sys -import typing -import warnings -from pathlib import Path - -import yaml - -import ops.charm -import ops.framework -import ops.model -import ops.storage - -from ops.log import setup_root_logging -from ops.jujuversion import JujuVersion - -CHARM_STATE_FILE = '.unit-state.db' - - -logger = logging.getLogger() - - -def _exe_path(path: Path) -> typing.Optional[Path]: - """Find and return the full path to the given binary. - - Here path is the absolute path to a binary, but might be missing an extension. - """ - p = shutil.which(path.name, mode=os.F_OK, path=str(path.parent)) - if p is None: - return None - return Path(p) - - -def _get_charm_dir(): - charm_dir = os.environ.get("JUJU_CHARM_DIR") - if charm_dir is None: - # Assume $JUJU_CHARM_DIR/lib/op/main.py structure. - charm_dir = Path('{}/../../..'.format(__file__)).resolve() - else: - charm_dir = Path(charm_dir).resolve() - return charm_dir - - -def _create_event_link(charm, bound_event, link_to): - """Create a symlink for a particular event. - - charm -- A charm object. - bound_event -- An event for which to create a symlink. - link_to -- What the event link should point to - """ - if issubclass(bound_event.event_type, ops.charm.HookEvent): - event_dir = charm.framework.charm_dir / 'hooks' - event_path = event_dir / bound_event.event_kind.replace('_', '-') - elif issubclass(bound_event.event_type, ops.charm.ActionEvent): - if not bound_event.event_kind.endswith("_action"): - raise RuntimeError( - 'action event name {} needs _action suffix'.format(bound_event.event_kind)) - event_dir = charm.framework.charm_dir / 'actions' - # The event_kind is suffixed with "_action" while the executable is not. - event_path = event_dir / bound_event.event_kind[:-len('_action')].replace('_', '-') - else: - raise RuntimeError( - 'cannot create a symlink: unsupported event type {}'.format(bound_event.event_type)) - - event_dir.mkdir(exist_ok=True) - if not event_path.exists(): - target_path = os.path.relpath(link_to, str(event_dir)) - - # Ignore the non-symlink files or directories - # assuming the charm author knows what they are doing. - logger.debug( - 'Creating a new relative symlink at %s pointing to %s', - event_path, target_path) - event_path.symlink_to(target_path) - - -def _setup_event_links(charm_dir, charm): - """Set up links for supported events that originate from Juju. - - Whether a charm can handle an event or not can be determined by - introspecting which events are defined on it. - - Hooks or actions are created as symlinks to the charm code file - which is determined by inspecting symlinks provided by the charm - author at hooks/install or hooks/start. - - charm_dir -- A root directory of the charm. - charm -- An instance of the Charm class. - - """ - # XXX: on windows this function does not accomplish what it wants to: - # it creates symlinks with no extension pointing to a .py - # and juju only knows how to handle .exe, .bat, .cmd, and .ps1 - # so it does its job, but does not accomplish anything as the - # hooks aren't 'callable'. - link_to = os.path.realpath(os.environ.get("JUJU_DISPATCH_PATH", sys.argv[0])) - for bound_event in charm.on.events().values(): - # Only events that originate from Juju need symlinks. - if issubclass(bound_event.event_type, (ops.charm.HookEvent, ops.charm.ActionEvent)): - _create_event_link(charm, bound_event, link_to) - - -def _emit_charm_event(charm, event_name): - """Emits a charm event based on a Juju event name. - - charm -- A charm instance to emit an event from. - event_name -- A Juju event name to emit on a charm. - """ - event_to_emit = None - try: - event_to_emit = getattr(charm.on, event_name) - except AttributeError: - logger.debug("Event %s not defined for %s.", event_name, charm) - - # If the event is not supported by the charm implementation, do - # not error out or try to emit it. This is to support rollbacks. - if event_to_emit is not None: - args, kwargs = _get_event_args(charm, event_to_emit) - logger.debug('Emitting Juju event %s.', event_name) - event_to_emit.emit(*args, **kwargs) - - -def _get_event_args(charm, bound_event): - event_type = bound_event.event_type - model = charm.framework.model - - if issubclass(event_type, ops.charm.RelationEvent): - relation_name = os.environ['JUJU_RELATION'] - relation_id = int(os.environ['JUJU_RELATION_ID'].split(':')[-1]) - relation = model.get_relation(relation_name, relation_id) - else: - relation = None - - remote_app_name = os.environ.get('JUJU_REMOTE_APP', '') - remote_unit_name = os.environ.get('JUJU_REMOTE_UNIT', '') - if remote_app_name or remote_unit_name: - if not remote_app_name: - if '/' not in remote_unit_name: - raise RuntimeError('invalid remote unit name: {}'.format(remote_unit_name)) - remote_app_name = remote_unit_name.split('/')[0] - args = [relation, model.get_app(remote_app_name)] - if remote_unit_name: - args.append(model.get_unit(remote_unit_name)) - return args, {} - elif relation: - return [relation], {} - return [], {} - - -class _Dispatcher: - """Encapsulate how to figure out what event Juju wants us to run. - - Also knows how to run “legacy” hooks when Juju called us via a top-level - ``dispatch`` binary. - - Args: - charm_dir: the toplevel directory of the charm - - Attributes: - event_name: the name of the event to run - is_dispatch_aware: are we running under a Juju that knows about the - dispatch binary, and is that binary present? - - """ - - def __init__(self, charm_dir: Path): - self._charm_dir = charm_dir - self._exec_path = Path(os.environ.get('JUJU_DISPATCH_PATH', sys.argv[0])) - - dispatch = charm_dir / 'dispatch' - if JujuVersion.from_environ().is_dispatch_aware() and _exe_path(dispatch) is not None: - self._init_dispatch() - else: - self._init_legacy() - - def ensure_event_links(self, charm): - """Make sure necessary symlinks are present on disk""" - - if self.is_dispatch_aware: - # links aren't needed - return - - # When a charm is force-upgraded and a unit is in an error state Juju - # does not run upgrade-charm and instead runs the failed hook followed - # by config-changed. Given the nature of force-upgrading the hook setup - # code is not triggered on config-changed. - # - # 'start' event is included as Juju does not fire the install event for - # K8s charms (see LP: #1854635). - if (self.event_name in ('install', 'start', 'upgrade_charm') - or self.event_name.endswith('_storage_attached')): - _setup_event_links(self._charm_dir, charm) - - def run_any_legacy_hook(self): - """Run any extant legacy hook. - - If there is both a dispatch file and a legacy hook for the - current event, run the wanted legacy hook. - """ - - if not self.is_dispatch_aware: - # we *are* the legacy hook - return - - dispatch_path = _exe_path(self._charm_dir / self._dispatch_path) - if dispatch_path is None: - logger.debug("Legacy %s does not exist.", self._dispatch_path) - return - - # super strange that there isn't an is_executable - if not os.access(str(dispatch_path), os.X_OK): - logger.warning("Legacy %s exists but is not executable.", self._dispatch_path) - return - - if dispatch_path.resolve() == Path(sys.argv[0]).resolve(): - logger.debug("Legacy %s is just a link to ourselves.", self._dispatch_path) - return - - argv = sys.argv.copy() - argv[0] = str(dispatch_path) - logger.info("Running legacy %s.", self._dispatch_path) - try: - subprocess.run(argv, check=True) - except subprocess.CalledProcessError as e: - logger.warning("Legacy %s exited with status %d.", self._dispatch_path, e.returncode) - sys.exit(e.returncode) - except OSError as e: - logger.warning("Unable to run legacy %s: %s", self._dispatch_path, e) - sys.exit(1) - else: - logger.debug("Legacy %s exited with status 0.", self._dispatch_path) - - def _set_name_from_path(self, path: Path): - """Sets the name attribute to that which can be inferred from the given path.""" - name = path.name.replace('-', '_') - if path.parent.name == 'actions': - name = '{}_action'.format(name) - self.event_name = name - - def _init_legacy(self): - """Set up the 'legacy' dispatcher. - - The current Juju doesn't know about 'dispatch' and calls hooks - explicitly. - """ - self.is_dispatch_aware = False - self._set_name_from_path(self._exec_path) - - def _init_dispatch(self): - """Set up the new 'dispatch' dispatcher. - - The current Juju will run 'dispatch' if it exists, and otherwise fall - back to the old behaviour. - - JUJU_DISPATCH_PATH will be set to the wanted hook, e.g. hooks/install, - in both cases. - """ - self._dispatch_path = Path(os.environ['JUJU_DISPATCH_PATH']) - - if 'OPERATOR_DISPATCH' in os.environ: - logger.debug("Charm called itself via %s.", self._dispatch_path) - sys.exit(0) - os.environ['OPERATOR_DISPATCH'] = '1' - - self.is_dispatch_aware = True - self._set_name_from_path(self._dispatch_path) - - def is_restricted_context(self): - """"Return True if we are running in a restricted Juju context. - - When in a restricted context, most commands (relation-get, config-get, - state-get) are not available. As such, we change how we interact with - Juju. - """ - return self.event_name in ('collect_metrics',) - - -def _should_use_controller_storage(db_path: Path, meta: ops.charm.CharmMeta) -> bool: - """Figure out whether we want to use controller storage or not.""" - # if you've previously used local state, carry on using that - if db_path.exists(): - logger.debug("Using local storage: %s already exists", db_path) - return False - - # if you're not in k8s you don't need controller storage - if 'kubernetes' not in meta.series: - logger.debug("Using local storage: not a kubernetes charm") - return False - - # are we in a new enough Juju? - cur_version = JujuVersion.from_environ() - - if cur_version.has_controller_storage(): - logger.debug("Using controller storage: JUJU_VERSION=%s", cur_version) - return True - else: - logger.debug("Using local storage: JUJU_VERSION=%s", cur_version) - return False - - -def main(charm_class: ops.charm.CharmBase, use_juju_for_storage: bool = None): - """Setup the charm and dispatch the observed event. - - The event name is based on the way this executable was called (argv[0]). - - Args: - charm_class: your charm class. - use_juju_for_storage: whether to use controller-side storage. If not specified - then kubernetes charms that haven't previously used local storage and that - are running on a new enough Juju default to controller-side storage, - otherwise local storage is used. - """ - charm_dir = _get_charm_dir() - - model_backend = ops.model._ModelBackend() - debug = ('JUJU_DEBUG' in os.environ) - setup_root_logging(model_backend, debug=debug) - logger.debug("Operator Framework %s up and running.", ops.__version__) - - dispatcher = _Dispatcher(charm_dir) - dispatcher.run_any_legacy_hook() - - metadata = (charm_dir / 'metadata.yaml').read_text() - actions_meta = charm_dir / 'actions.yaml' - if actions_meta.exists(): - actions_metadata = actions_meta.read_text() - else: - actions_metadata = None - - if not yaml.__with_libyaml__: - logger.debug('yaml does not have libyaml extensions, using slower pure Python yaml loader') - meta = ops.charm.CharmMeta.from_yaml(metadata, actions_metadata) - model = ops.model.Model(meta, model_backend) - - charm_state_path = charm_dir / CHARM_STATE_FILE - - if use_juju_for_storage and not ops.storage.juju_backend_available(): - # raise an exception; the charm is broken and needs fixing. - msg = 'charm set use_juju_for_storage=True, but Juju version {} does not support it' - raise RuntimeError(msg.format(JujuVersion.from_environ())) - - if use_juju_for_storage is None: - use_juju_for_storage = _should_use_controller_storage(charm_state_path, meta) - - if use_juju_for_storage: - if dispatcher.is_restricted_context(): - # TODO: jam 2020-06-30 This unconditionally avoids running a collect metrics event - # Though we eventually expect that juju will run collect-metrics in a - # non-restricted context. Once we can determine that we are running collect-metrics - # in a non-restricted context, we should fire the event as normal. - logger.debug('"%s" is not supported when using Juju for storage\n' - 'see: https://github.com/canonical/operator/issues/348', - dispatcher.event_name) - # Note that we don't exit nonzero, because that would cause Juju to rerun the hook - return - store = ops.storage.JujuStorage() - else: - store = ops.storage.SQLiteStorage(charm_state_path) - framework = ops.framework.Framework(store, charm_dir, meta, model) - try: - sig = inspect.signature(charm_class) - try: - sig.bind(framework) - except TypeError: - msg = ( - "the second argument, 'key', has been deprecated and will be " - "removed after the 0.7 release") - warnings.warn(msg, DeprecationWarning) - charm = charm_class(framework, None) - else: - charm = charm_class(framework) - dispatcher.ensure_event_links(charm) - - # TODO: Remove the collect_metrics check below as soon as the relevant - # Juju changes are made. - # - # Skip reemission of deferred events for collect-metrics events because - # they do not have the full access to all hook tools. - if not dispatcher.is_restricted_context(): - framework.reemit() - - _emit_charm_event(charm, dispatcher.event_name) - - framework.commit() - finally: - framework.close() diff --git a/coredns/venv/ops/model.py b/coredns/venv/ops/model.py deleted file mode 100644 index 55addf5..0000000 --- a/coredns/venv/ops/model.py +++ /dev/null @@ -1,1284 +0,0 @@ -# Copyright 2019 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime -import decimal -import ipaddress -import json -import os -import re -import shutil -import tempfile -import time -import typing -import weakref - -from abc import ABC, abstractmethod -from collections.abc import Mapping, MutableMapping -from pathlib import Path -from subprocess import run, PIPE, CalledProcessError -import yaml - -import ops -from ops.jujuversion import JujuVersion - - -if yaml.__with_libyaml__: - _DefaultDumper = yaml.CSafeDumper -else: - _DefaultDumper = yaml.SafeDumper - - -class Model: - """Represents the Juju Model as seen from this unit. - - This should not be instantiated directly by Charmers, but can be accessed as `self.model` - from any class that derives from Object. - """ - - def __init__(self, meta: 'ops.charm.CharmMeta', backend: '_ModelBackend'): - self._cache = _ModelCache(backend) - self._backend = backend - self._unit = self.get_unit(self._backend.unit_name) - self._relations = RelationMapping(meta.relations, self.unit, self._backend, self._cache) - self._config = ConfigData(self._backend) - self._resources = Resources(list(meta.resources), self._backend) - self._pod = Pod(self._backend) - self._storages = StorageMapping(list(meta.storages), self._backend) - self._bindings = BindingMapping(self._backend) - - @property - def unit(self) -> 'Unit': - """A :class:`Unit` that represents the unit that is running this code (eg yourself)""" - return self._unit - - @property - def app(self): - """A :class:`Application` that represents the application this unit is a part of.""" - return self._unit.app - - @property - def relations(self) -> 'RelationMapping': - """Mapping of endpoint to list of :class:`Relation` - - Answers the question "what am I currently related to". - See also :meth:`.get_relation`. - """ - return self._relations - - @property - def config(self) -> 'ConfigData': - """Return a mapping of config for the current application.""" - return self._config - - @property - def resources(self) -> 'Resources': - """Access to resources for this charm. - - Use ``model.resources.fetch(resource_name)`` to get the path on disk - where the resource can be found. - """ - return self._resources - - @property - def storages(self) -> 'StorageMapping': - """Mapping of storage_name to :class:`Storage` as defined in metadata.yaml""" - return self._storages - - @property - def pod(self) -> 'Pod': - """Use ``model.pod.set_spec`` to set the container specification for Kubernetes charms.""" - return self._pod - - @property - def name(self) -> str: - """Return the name of the Model that this unit is running in. - - This is read from the environment variable ``JUJU_MODEL_NAME``. - """ - return self._backend.model_name - - def get_unit(self, unit_name: str) -> 'Unit': - """Get an arbitrary unit by name. - - Internally this uses a cache, so asking for the same unit two times will - return the same object. - """ - return self._cache.get(Unit, unit_name) - - def get_app(self, app_name: str) -> 'Application': - """Get an application by name. - - Internally this uses a cache, so asking for the same application two times will - return the same object. - """ - return self._cache.get(Application, app_name) - - def get_relation( - self, relation_name: str, - relation_id: typing.Optional[int] = None) -> 'Relation': - """Get a specific Relation instance. - - If relation_id is not given, this will return the Relation instance if the - relation is established only once or None if it is not established. If this - same relation is established multiple times the error TooManyRelatedAppsError is raised. - - Args: - relation_name: The name of the endpoint for this charm - relation_id: An identifier for a specific relation. Used to disambiguate when a - given application has more than one relation on a given endpoint. - Raises: - TooManyRelatedAppsError: is raised if there is more than one relation to the - supplied relation_name and no relation_id was supplied - """ - return self.relations._get_unique(relation_name, relation_id) - - def get_binding(self, binding_key: typing.Union[str, 'Relation']) -> 'Binding': - """Get a network space binding. - - Args: - binding_key: The relation name or instance to obtain bindings for. - Returns: - If ``binding_key`` is a relation name, the method returns the default binding - for that relation. If a relation instance is provided, the method first looks - up a more specific binding for that specific relation ID, and if none is found - falls back to the default binding for the relation name. - """ - return self._bindings.get(binding_key) - - -class _ModelCache: - - def __init__(self, backend): - self._backend = backend - self._weakrefs = weakref.WeakValueDictionary() - - def get(self, entity_type, *args): - key = (entity_type,) + args - entity = self._weakrefs.get(key) - if entity is None: - entity = entity_type(*args, backend=self._backend, cache=self) - self._weakrefs[key] = entity - return entity - - -class Application: - """Represents a named application in the model. - - This might be your application, or might be an application that you are related to. - Charmers should not instantiate Application objects directly, but should use - :meth:`Model.get_app` if they need a reference to a given application. - - Attributes: - name: The name of this application (eg, 'mysql'). This name may differ from the name of - the charm, if the user has deployed it to a different name. - """ - - def __init__(self, name, backend, cache): - self.name = name - self._backend = backend - self._cache = cache - self._is_our_app = self.name == self._backend.app_name - self._status = None - - def _invalidate(self): - self._status = None - - @property - def status(self) -> 'StatusBase': - """Used to report or read the status of the overall application. - - Can only be read and set by the lead unit of the application. - - The status of remote units is always Unknown. - - Raises: - RuntimeError: if you try to set the status of another application, or if you try to - set the status of this application as a unit that is not the leader. - InvalidStatusError: if you try to set the status to something that is not a - :class:`StatusBase` - - Example:: - - self.model.app.status = BlockedStatus('I need a human to come help me') - """ - if not self._is_our_app: - return UnknownStatus() - - if not self._backend.is_leader(): - raise RuntimeError('cannot get application status as a non-leader unit') - - if self._status: - return self._status - - s = self._backend.status_get(is_app=True) - self._status = StatusBase.from_name(s['status'], s['message']) - return self._status - - @status.setter - def status(self, value: 'StatusBase'): - if not isinstance(value, StatusBase): - raise InvalidStatusError( - 'invalid value provided for application {} status: {}'.format(self, value) - ) - - if not self._is_our_app: - raise RuntimeError('cannot to set status for a remote application {}'.format(self)) - - if not self._backend.is_leader(): - raise RuntimeError('cannot set application status as a non-leader unit') - - self._backend.status_set(value.name, value.message, is_app=True) - self._status = value - - def __repr__(self): - return '<{}.{} {}>'.format(type(self).__module__, type(self).__name__, self.name) - - -class Unit: - """Represents a named unit in the model. - - This might be your unit, another unit of your application, or a unit of another application - that you are related to. - - Attributes: - name: The name of the unit (eg, 'mysql/0') - app: The Application the unit is a part of. - """ - - def __init__(self, name, backend, cache): - self.name = name - - app_name = name.split('/')[0] - self.app = cache.get(Application, app_name) - - self._backend = backend - self._cache = cache - self._is_our_unit = self.name == self._backend.unit_name - self._status = None - - def _invalidate(self): - self._status = None - - @property - def status(self) -> 'StatusBase': - """Used to report or read the status of a specific unit. - - The status of any unit other than yourself is always Unknown. - - Raises: - RuntimeError: if you try to set the status of a unit other than yourself. - InvalidStatusError: if you try to set the status to something other than - a :class:`StatusBase` - Example:: - - self.model.unit.status = MaintenanceStatus('reconfiguring the frobnicators') - """ - if not self._is_our_unit: - return UnknownStatus() - - if self._status: - return self._status - - s = self._backend.status_get(is_app=False) - self._status = StatusBase.from_name(s['status'], s['message']) - return self._status - - @status.setter - def status(self, value: 'StatusBase'): - if not isinstance(value, StatusBase): - raise InvalidStatusError( - 'invalid value provided for unit {} status: {}'.format(self, value) - ) - - if not self._is_our_unit: - raise RuntimeError('cannot set status for a remote unit {}'.format(self)) - - self._backend.status_set(value.name, value.message, is_app=False) - self._status = value - - def __repr__(self): - return '<{}.{} {}>'.format(type(self).__module__, type(self).__name__, self.name) - - def is_leader(self) -> bool: - """Return whether this unit is the leader of its application. - - This can only be called for your own unit. - Returns: - True if you are the leader, False otherwise - Raises: - RuntimeError: if called for a unit that is not yourself - """ - if self._is_our_unit: - # This value is not cached as it is not guaranteed to persist for the whole duration - # of a hook execution. - return self._backend.is_leader() - else: - raise RuntimeError( - 'leadership status of remote units ({}) is not visible to other' - ' applications'.format(self) - ) - - def set_workload_version(self, version: str) -> None: - """Record the version of the software running as the workload. - - This shouldn't be confused with the revision of the charm. This is informative only; - shown in the output of 'juju status'. - """ - if not isinstance(version, str): - raise TypeError("workload version must be a str, not {}: {!r}".format( - type(version).__name__, version)) - self._backend.application_version_set(version) - - -class LazyMapping(Mapping, ABC): - """Represents a dict that isn't populated until it is accessed. - - Charm authors should generally never need to use this directly, but it forms - the basis for many of the dicts that the framework tracks. - """ - - _lazy_data = None - - @abstractmethod - def _load(self): - raise NotImplementedError() - - @property - def _data(self): - data = self._lazy_data - if data is None: - data = self._lazy_data = self._load() - return data - - def _invalidate(self): - self._lazy_data = None - - def __contains__(self, key): - return key in self._data - - def __len__(self): - return len(self._data) - - def __iter__(self): - return iter(self._data) - - def __getitem__(self, key): - return self._data[key] - - def __repr__(self): - return repr(self._data) - - -class RelationMapping(Mapping): - """Map of relation names to lists of :class:`Relation` instances.""" - - def __init__(self, relations_meta, our_unit, backend, cache): - self._peers = set() - for name, relation_meta in relations_meta.items(): - if relation_meta.role.is_peer(): - self._peers.add(name) - self._our_unit = our_unit - self._backend = backend - self._cache = cache - self._data = {relation_name: None for relation_name in relations_meta} - - def __contains__(self, key): - return key in self._data - - def __len__(self): - return len(self._data) - - def __iter__(self): - return iter(self._data) - - def __getitem__(self, relation_name): - is_peer = relation_name in self._peers - relation_list = self._data[relation_name] - if relation_list is None: - relation_list = self._data[relation_name] = [] - for rid in self._backend.relation_ids(relation_name): - relation = Relation(relation_name, rid, is_peer, - self._our_unit, self._backend, self._cache) - relation_list.append(relation) - return relation_list - - def _invalidate(self, relation_name): - """Used to wipe the cache of a given relation_name. - - Not meant to be used by Charm authors. The content of relation data is - static for the lifetime of a hook, so it is safe to cache in memory once - accessed. - """ - self._data[relation_name] = None - - def _get_unique(self, relation_name, relation_id=None): - if relation_id is not None: - if not isinstance(relation_id, int): - raise ModelError('relation id {} must be int or None not {}'.format( - relation_id, - type(relation_id).__name__)) - for relation in self[relation_name]: - if relation.id == relation_id: - return relation - else: - # The relation may be dead, but it is not forgotten. - is_peer = relation_name in self._peers - return Relation(relation_name, relation_id, is_peer, - self._our_unit, self._backend, self._cache) - num_related = len(self[relation_name]) - if num_related == 0: - return None - elif num_related == 1: - return self[relation_name][0] - else: - # TODO: We need something in the framework to catch and gracefully handle - # errors, ideally integrating the error catching with Juju's mechanisms. - raise TooManyRelatedAppsError(relation_name, num_related, 1) - - -class BindingMapping: - """Mapping of endpoints to network bindings. - - Charm authors should not instantiate this directly, but access it via - :meth:`Model.get_binding` - """ - - def __init__(self, backend): - self._backend = backend - self._data = {} - - def get(self, binding_key: typing.Union[str, 'Relation']) -> 'Binding': - """Get a specific Binding for an endpoint/relation. - - Not used directly by Charm authors. See :meth:`Model.get_binding` - """ - if isinstance(binding_key, Relation): - binding_name = binding_key.name - relation_id = binding_key.id - elif isinstance(binding_key, str): - binding_name = binding_key - relation_id = None - else: - raise ModelError('binding key must be str or relation instance, not {}' - ''.format(type(binding_key).__name__)) - binding = self._data.get(binding_key) - if binding is None: - binding = Binding(binding_name, relation_id, self._backend) - self._data[binding_key] = binding - return binding - - -class Binding: - """Binding to a network space. - - Attributes: - name: The name of the endpoint this binding represents (eg, 'db') - """ - - def __init__(self, name, relation_id, backend): - self.name = name - self._relation_id = relation_id - self._backend = backend - self._network = None - - @property - def network(self) -> 'Network': - """The network information for this binding.""" - if self._network is None: - try: - self._network = Network(self._backend.network_get(self.name, self._relation_id)) - except RelationNotFoundError: - if self._relation_id is None: - raise - # If a relation is dead, we can still get network info associated with an - # endpoint itself - self._network = Network(self._backend.network_get(self.name)) - return self._network - - -class Network: - """Network space details. - - Charm authors should not instantiate this directly, but should get access to the Network - definition from :meth:`Model.get_binding` and its ``network`` attribute. - - Attributes: - interfaces: A list of :class:`NetworkInterface` details. This includes the - information about how your application should be configured (eg, what - IP addresses should you bind to.) - Note that multiple addresses for a single interface are represented as multiple - interfaces. (eg, ``[NetworkInfo('ens1', '10.1.1.1/32'), - NetworkInfo('ens1', '10.1.2.1/32'])``) - ingress_addresses: A list of :class:`ipaddress.ip_address` objects representing the IP - addresses that other units should use to get in touch with you. - egress_subnets: A list of :class:`ipaddress.ip_network` representing the subnets that - other units will see you connecting from. Due to things like NAT it isn't always - possible to narrow it down to a single address, but when it is clear, the CIDRs - will be constrained to a single address. (eg, 10.0.0.1/32) - Args: - network_info: A dict of network information as returned by ``network-get``. - """ - - def __init__(self, network_info: dict): - self.interfaces = [] - # Treat multiple addresses on an interface as multiple logical - # interfaces with the same name. - for interface_info in network_info['bind-addresses']: - interface_name = interface_info['interface-name'] - for address_info in interface_info['addresses']: - self.interfaces.append(NetworkInterface(interface_name, address_info)) - self.ingress_addresses = [] - for address in network_info['ingress-addresses']: - self.ingress_addresses.append(ipaddress.ip_address(address)) - self.egress_subnets = [] - for subnet in network_info['egress-subnets']: - self.egress_subnets.append(ipaddress.ip_network(subnet)) - - @property - def bind_address(self): - """A single address that your application should bind() to. - - For the common case where there is a single answer. This represents a single - address from :attr:`.interfaces` that can be used to configure where your - application should bind() and listen(). - """ - return self.interfaces[0].address - - @property - def ingress_address(self): - """The address other applications should use to connect to your unit. - - Due to things like public/private addresses, NAT and tunneling, the address you bind() - to is not always the address other people can use to connect() to you. - This is just the first address from :attr:`.ingress_addresses`. - """ - return self.ingress_addresses[0] - - -class NetworkInterface: - """Represents a single network interface that the charm needs to know about. - - Charmers should not instantiate this type directly. Instead use :meth:`Model.get_binding` - to get the network information for a given endpoint. - - Attributes: - name: The name of the interface (eg. 'eth0', or 'ens1') - subnet: An :class:`ipaddress.ip_network` representation of the IP for the network - interface. This may be a single address (eg '10.0.1.2/32') - """ - - def __init__(self, name: str, address_info: dict): - self.name = name - # TODO: expose a hardware address here, see LP: #1864070. - self.address = ipaddress.ip_address(address_info['value']) - cidr = address_info['cidr'] - if not cidr: - # The cidr field may be empty, see LP: #1864102. - # In this case, make it a /32 or /128 IP network. - self.subnet = ipaddress.ip_network(address_info['value']) - else: - self.subnet = ipaddress.ip_network(cidr) - # TODO: expose a hostname/canonical name for the address here, see LP: #1864086. - - -class Relation: - """Represents an established relation between this application and another application. - - This class should not be instantiated directly, instead use :meth:`Model.get_relation` - or :attr:`RelationEvent.relation`. - - Attributes: - name: The name of the local endpoint of the relation (eg 'db') - id: The identifier for a particular relation (integer) - app: An :class:`Application` representing the remote application of this relation. - For peer relations this will be the local application. - units: A set of :class:`Unit` for units that have started and joined this relation. - data: A :class:`RelationData` holding the data buckets for each entity - of a relation. Accessed via eg Relation.data[unit]['foo'] - """ - - def __init__( - self, relation_name: str, relation_id: int, is_peer: bool, our_unit: Unit, - backend: '_ModelBackend', cache: '_ModelCache'): - self.name = relation_name - self.id = relation_id - self.app = None - self.units = set() - - # For peer relations, both the remote and the local app are the same. - if is_peer: - self.app = our_unit.app - try: - for unit_name in backend.relation_list(self.id): - unit = cache.get(Unit, unit_name) - self.units.add(unit) - if self.app is None: - self.app = unit.app - except RelationNotFoundError: - # If the relation is dead, just treat it as if it has no remote units. - pass - self.data = RelationData(self, our_unit, backend) - - def __repr__(self): - return '<{}.{} {}:{}>'.format(type(self).__module__, - type(self).__name__, - self.name, - self.id) - - -class RelationData(Mapping): - """Represents the various data buckets of a given relation. - - Each unit and application involved in a relation has their own data bucket. - Eg: ``{entity: RelationDataContent}`` - where entity can be either a :class:`Unit` or a :class:`Application`. - - Units can read and write their own data, and if they are the leader, - they can read and write their application data. They are allowed to read - remote unit and application data. - - This class should not be created directly. It should be accessed via - :attr:`Relation.data` - """ - - def __init__(self, relation: Relation, our_unit: Unit, backend: '_ModelBackend'): - self.relation = weakref.proxy(relation) - self._data = { - our_unit: RelationDataContent(self.relation, our_unit, backend), - our_unit.app: RelationDataContent(self.relation, our_unit.app, backend), - } - self._data.update({ - unit: RelationDataContent(self.relation, unit, backend) - for unit in self.relation.units}) - # The relation might be dead so avoid a None key here. - if self.relation.app is not None: - self._data.update({ - self.relation.app: RelationDataContent(self.relation, self.relation.app, backend), - }) - - def __contains__(self, key): - return key in self._data - - def __len__(self): - return len(self._data) - - def __iter__(self): - return iter(self._data) - - def __getitem__(self, key): - return self._data[key] - - def __repr__(self): - return repr(self._data) - - -# We mix in MutableMapping here to get some convenience implementations, but whether it's actually -# mutable or not is controlled by the flag. -class RelationDataContent(LazyMapping, MutableMapping): - - def __init__(self, relation, entity, backend): - self.relation = relation - self._entity = entity - self._backend = backend - self._is_app = isinstance(entity, Application) - - def _load(self): - try: - return self._backend.relation_get(self.relation.id, self._entity.name, self._is_app) - except RelationNotFoundError: - # Dead relations tell no tales (and have no data). - return {} - - def _is_mutable(self): - if self._is_app: - is_our_app = self._backend.app_name == self._entity.name - if not is_our_app: - return False - # Whether the application data bag is mutable or not depends on - # whether this unit is a leader or not, but this is not guaranteed - # to be always true during the same hook execution. - return self._backend.is_leader() - else: - is_our_unit = self._backend.unit_name == self._entity.name - if is_our_unit: - return True - return False - - def __setitem__(self, key, value): - if not self._is_mutable(): - raise RelationDataError('cannot set relation data for {}'.format(self._entity.name)) - if not isinstance(value, str): - raise RelationDataError('relation data values must be strings') - - self._backend.relation_set(self.relation.id, key, value, self._is_app) - - # Don't load data unnecessarily if we're only updating. - if self._lazy_data is not None: - if value == '': - # Match the behavior of Juju, which is that setting the value to an - # empty string will remove the key entirely from the relation data. - self._data.pop(key, None) - else: - self._data[key] = value - - def __delitem__(self, key): - # Match the behavior of Juju, which is that setting the value to an empty - # string will remove the key entirely from the relation data. - self.__setitem__(key, '') - - -class ConfigData(LazyMapping): - - def __init__(self, backend): - self._backend = backend - - def _load(self): - return self._backend.config_get() - - -class StatusBase: - """Status values specific to applications and units. - - To access a status by name, see :meth:`StatusBase.from_name`, most use cases will just - directly use the child class to indicate their status. - """ - - _statuses = {} - name = None - - def __init__(self, message: str): - self.message = message - - def __new__(cls, *args, **kwargs): - if cls is StatusBase: - raise TypeError("cannot instantiate a base class") - return super().__new__(cls) - - def __eq__(self, other): - if not isinstance(self, type(other)): - return False - return self.message == other.message - - def __repr__(self): - return "{.__class__.__name__}({!r})".format(self, self.message) - - @classmethod - def from_name(cls, name: str, message: str): - if name == 'unknown': - # unknown is special - return UnknownStatus() - else: - return cls._statuses[name](message) - - @classmethod - def register(cls, child): - if child.name is None: - raise AttributeError('cannot register a Status which has no name') - cls._statuses[child.name] = child - return child - - -@StatusBase.register -class UnknownStatus(StatusBase): - """The unit status is unknown. - - A unit-agent has finished calling install, config-changed and start, but the - charm has not called status-set yet. - - """ - name = 'unknown' - - def __init__(self): - # Unknown status cannot be set and does not have a message associated with it. - super().__init__('') - - def __repr__(self): - return "UnknownStatus()" - - -@StatusBase.register -class ActiveStatus(StatusBase): - """The unit is ready. - - The unit believes it is correctly offering all the services it has been asked to offer. - """ - name = 'active' - - def __init__(self, message: str = ''): - super().__init__(message) - - -@StatusBase.register -class BlockedStatus(StatusBase): - """The unit requires manual intervention. - - An operator has to manually intervene to unblock the unit and let it proceed. - """ - name = 'blocked' - - -@StatusBase.register -class MaintenanceStatus(StatusBase): - """The unit is performing maintenance tasks. - - The unit is not yet providing services, but is actively doing work in preparation - for providing those services. This is a "spinning" state, not an error state. It - reflects activity on the unit itself, not on peers or related units. - - """ - name = 'maintenance' - - -@StatusBase.register -class WaitingStatus(StatusBase): - """A unit is unable to progress. - - The unit is unable to progress to an active state because an application to which - it is related is not running. - - """ - name = 'waiting' - - -class Resources: - """Object representing resources for the charm. - """ - - def __init__(self, names: typing.Iterable[str], backend: '_ModelBackend'): - self._backend = backend - self._paths = {name: None for name in names} - - def fetch(self, name: str) -> Path: - """Fetch the resource from the controller or store. - - If successfully fetched, this returns a Path object to where the resource is stored - on disk, otherwise it raises a ModelError. - """ - if name not in self._paths: - raise RuntimeError('invalid resource name: {}'.format(name)) - if self._paths[name] is None: - self._paths[name] = Path(self._backend.resource_get(name)) - return self._paths[name] - - -class Pod: - """Represents the definition of a pod spec in Kubernetes models. - - Currently only supports simple access to setting the Juju pod spec via :attr:`.set_spec`. - """ - - def __init__(self, backend: '_ModelBackend'): - self._backend = backend - - def set_spec(self, spec: typing.Mapping, k8s_resources: typing.Mapping = None): - """Set the specification for pods that Juju should start in kubernetes. - - See `juju help-tool pod-spec-set` for details of what should be passed. - - Args: - spec: The mapping defining the pod specification - k8s_resources: Additional kubernetes specific specification. - - Returns: - None - """ - if not self._backend.is_leader(): - raise ModelError('cannot set a pod spec as this unit is not a leader') - self._backend.pod_spec_set(spec, k8s_resources) - - -class StorageMapping(Mapping): - """Map of storage names to lists of Storage instances.""" - - def __init__(self, storage_names: typing.Iterable[str], backend: '_ModelBackend'): - self._backend = backend - self._storage_map = {storage_name: None for storage_name in storage_names} - - def __contains__(self, key: str): - return key in self._storage_map - - def __len__(self): - return len(self._storage_map) - - def __iter__(self): - return iter(self._storage_map) - - def __getitem__(self, storage_name: str) -> typing.List['Storage']: - storage_list = self._storage_map[storage_name] - if storage_list is None: - storage_list = self._storage_map[storage_name] = [] - for storage_id in self._backend.storage_list(storage_name): - storage_list.append(Storage(storage_name, storage_id, self._backend)) - return storage_list - - def request(self, storage_name: str, count: int = 1): - """Requests new storage instances of a given name. - - Uses storage-add tool to request additional storage. Juju will notify the unit - via -storage-attached events when it becomes available. - """ - if storage_name not in self._storage_map: - raise ModelError(('cannot add storage {!r}:' - ' it is not present in the charm metadata').format(storage_name)) - self._backend.storage_add(storage_name, count) - - -class Storage: - """"Represents a storage as defined in metadata.yaml - - Attributes: - name: Simple string name of the storage - id: The provider id for storage - """ - - def __init__(self, storage_name, storage_id, backend): - self.name = storage_name - self.id = storage_id - self._backend = backend - self._location = None - - @property - def location(self): - if self._location is None: - raw = self._backend.storage_get('{}/{}'.format(self.name, self.id), "location") - self._location = Path(raw) - return self._location - - -class ModelError(Exception): - """Base class for exceptions raised when interacting with the Model.""" - pass - - -class TooManyRelatedAppsError(ModelError): - """Raised by :meth:`Model.get_relation` if there is more than one related application.""" - - def __init__(self, relation_name, num_related, max_supported): - super().__init__('Too many remote applications on {} ({} > {})'.format( - relation_name, num_related, max_supported)) - self.relation_name = relation_name - self.num_related = num_related - self.max_supported = max_supported - - -class RelationDataError(ModelError): - """Raised by ``Relation.data[entity][key] = 'foo'`` if the data is invalid. - - This is raised if you're either trying to set a value to something that isn't a string, - or if you are trying to set a value in a bucket that you don't have access to. (eg, - another application/unit or setting your application data but you aren't the leader.) - """ - - -class RelationNotFoundError(ModelError): - """Backend error when querying juju for a given relation and that relation doesn't exist.""" - - -class InvalidStatusError(ModelError): - """Raised if trying to set an Application or Unit status to something invalid.""" - - -class _ModelBackend: - """Represents the connection between the Model representation and talking to Juju. - - Charm authors should not directly interact with the ModelBackend, it is a private - implementation of Model. - """ - - LEASE_RENEWAL_PERIOD = datetime.timedelta(seconds=30) - - def __init__(self, unit_name=None, model_name=None): - if unit_name is None: - self.unit_name = os.environ['JUJU_UNIT_NAME'] - else: - self.unit_name = unit_name - if model_name is None: - model_name = os.environ.get('JUJU_MODEL_NAME') - self.model_name = model_name - self.app_name = self.unit_name.split('/')[0] - - self._is_leader = None - self._leader_check_time = None - - def _run(self, *args, return_output=False, use_json=False): - kwargs = dict(stdout=PIPE, stderr=PIPE, check=True) - args = (shutil.which(args[0]),) + args[1:] - if use_json: - args += ('--format=json',) - try: - result = run(args, **kwargs) - except CalledProcessError as e: - raise ModelError(e.stderr) - if return_output: - if result.stdout is None: - return '' - else: - text = result.stdout.decode('utf8') - if use_json: - return json.loads(text) - else: - return text - - def relation_ids(self, relation_name): - relation_ids = self._run('relation-ids', relation_name, return_output=True, use_json=True) - return [int(relation_id.split(':')[-1]) for relation_id in relation_ids] - - def relation_list(self, relation_id): - try: - return self._run('relation-list', '-r', str(relation_id), - return_output=True, use_json=True) - except ModelError as e: - if 'relation not found' in str(e): - raise RelationNotFoundError() from e - raise - - def relation_get(self, relation_id, member_name, is_app): - if not isinstance(is_app, bool): - raise TypeError('is_app parameter to relation_get must be a boolean') - - if is_app: - version = JujuVersion.from_environ() - if not version.has_app_data(): - raise RuntimeError( - 'getting application data is not supported on Juju version {}'.format(version)) - - args = ['relation-get', '-r', str(relation_id), '-', member_name] - if is_app: - args.append('--app') - - try: - return self._run(*args, return_output=True, use_json=True) - except ModelError as e: - if 'relation not found' in str(e): - raise RelationNotFoundError() from e - raise - - def relation_set(self, relation_id, key, value, is_app): - if not isinstance(is_app, bool): - raise TypeError('is_app parameter to relation_set must be a boolean') - - if is_app: - version = JujuVersion.from_environ() - if not version.has_app_data(): - raise RuntimeError( - 'setting application data is not supported on Juju version {}'.format(version)) - - args = ['relation-set', '-r', str(relation_id), '{}={}'.format(key, value)] - if is_app: - args.append('--app') - - try: - return self._run(*args) - except ModelError as e: - if 'relation not found' in str(e): - raise RelationNotFoundError() from e - raise - - def config_get(self): - return self._run('config-get', return_output=True, use_json=True) - - def is_leader(self): - """Obtain the current leadership status for the unit the charm code is executing on. - - The value is cached for the duration of a lease which is 30s in Juju. - """ - now = time.monotonic() - if self._leader_check_time is None: - check = True - else: - time_since_check = datetime.timedelta(seconds=now - self._leader_check_time) - check = (time_since_check > self.LEASE_RENEWAL_PERIOD or self._is_leader is None) - if check: - # Current time MUST be saved before running is-leader to ensure the cache - # is only used inside the window that is-leader itself asserts. - self._leader_check_time = now - self._is_leader = self._run('is-leader', return_output=True, use_json=True) - - return self._is_leader - - def resource_get(self, resource_name): - return self._run('resource-get', resource_name, return_output=True).strip() - - def pod_spec_set(self, spec, k8s_resources): - tmpdir = Path(tempfile.mkdtemp('-pod-spec-set')) - try: - spec_path = tmpdir / 'spec.yaml' - with spec_path.open("wt", encoding="utf8") as f: - yaml.dump(spec, stream=f, Dumper=_DefaultDumper) - args = ['--file', str(spec_path)] - if k8s_resources: - k8s_res_path = tmpdir / 'k8s-resources.yaml' - with k8s_res_path.open("wt", encoding="utf8") as f: - yaml.dump(k8s_resources, stream=f, Dumper=_DefaultDumper) - args.extend(['--k8s-resources', str(k8s_res_path)]) - self._run('pod-spec-set', *args) - finally: - shutil.rmtree(str(tmpdir)) - - def status_get(self, *, is_app=False): - """Get a status of a unit or an application. - - Args: - is_app: A boolean indicating whether the status should be retrieved for a unit - or an application. - """ - content = self._run( - 'status-get', '--include-data', '--application={}'.format(is_app), - use_json=True, - return_output=True) - # Unit status looks like (in YAML): - # message: 'load: 0.28 0.26 0.26' - # status: active - # status-data: {} - # Application status looks like (in YAML): - # application-status: - # message: 'load: 0.28 0.26 0.26' - # status: active - # status-data: {} - # units: - # uo/0: - # message: 'load: 0.28 0.26 0.26' - # status: active - # status-data: {} - - if is_app: - return {'status': content['application-status']['status'], - 'message': content['application-status']['message']} - else: - return content - - def status_set(self, status, message='', *, is_app=False): - """Set a status of a unit or an application. - - Args: - app: A boolean indicating whether the status should be set for a unit or an - application. - """ - if not isinstance(is_app, bool): - raise TypeError('is_app parameter must be boolean') - return self._run('status-set', '--application={}'.format(is_app), status, message) - - def storage_list(self, name): - return [int(s.split('/')[1]) for s in self._run('storage-list', name, - return_output=True, use_json=True)] - - def storage_get(self, storage_name_id, attribute): - return self._run('storage-get', '-s', storage_name_id, attribute, - return_output=True, use_json=True) - - def storage_add(self, name, count=1): - if not isinstance(count, int) or isinstance(count, bool): - raise TypeError('storage count must be integer, got: {} ({})'.format(count, - type(count))) - self._run('storage-add', '{}={}'.format(name, count)) - - def action_get(self): - return self._run('action-get', return_output=True, use_json=True) - - def action_set(self, results): - self._run('action-set', *["{}={}".format(k, v) for k, v in results.items()]) - - def action_log(self, message): - self._run('action-log', message) - - def action_fail(self, message=''): - self._run('action-fail', message) - - def application_version_set(self, version): - self._run('application-version-set', '--', version) - - def juju_log(self, level, message): - self._run('juju-log', '--log-level', level, message) - - def network_get(self, binding_name, relation_id=None): - """Return network info provided by network-get for a given binding. - - Args: - binding_name: A name of a binding (relation name or extra-binding name). - relation_id: An optional relation id to get network info for. - """ - cmd = ['network-get', binding_name] - if relation_id is not None: - cmd.extend(['-r', str(relation_id)]) - try: - return self._run(*cmd, return_output=True, use_json=True) - except ModelError as e: - if 'relation not found' in str(e): - raise RelationNotFoundError() from e - raise - - def add_metrics(self, metrics, labels=None): - cmd = ['add-metric'] - - if labels: - label_args = [] - for k, v in labels.items(): - _ModelBackendValidator.validate_metric_label(k) - _ModelBackendValidator.validate_label_value(k, v) - label_args.append('{}={}'.format(k, v)) - cmd.extend(['--labels', ','.join(label_args)]) - - metric_args = [] - for k, v in metrics.items(): - _ModelBackendValidator.validate_metric_key(k) - metric_value = _ModelBackendValidator.format_metric_value(v) - metric_args.append('{}={}'.format(k, metric_value)) - cmd.extend(metric_args) - self._run(*cmd) - - -class _ModelBackendValidator: - """Provides facilities for validating inputs and formatting them for model backends.""" - - METRIC_KEY_REGEX = re.compile(r'^[a-zA-Z](?:[a-zA-Z0-9-_]*[a-zA-Z0-9])?$') - - @classmethod - def validate_metric_key(cls, key): - if cls.METRIC_KEY_REGEX.match(key) is None: - raise ModelError( - 'invalid metric key {!r}: must match {}'.format( - key, cls.METRIC_KEY_REGEX.pattern)) - - @classmethod - def validate_metric_label(cls, label_name): - if cls.METRIC_KEY_REGEX.match(label_name) is None: - raise ModelError( - 'invalid metric label name {!r}: must match {}'.format( - label_name, cls.METRIC_KEY_REGEX.pattern)) - - @classmethod - def format_metric_value(cls, value): - try: - decimal_value = decimal.Decimal.from_float(value) - except TypeError as e: - e2 = ModelError('invalid metric value {!r} provided:' - ' must be a positive finite float'.format(value)) - raise e2 from e - if decimal_value.is_nan() or decimal_value.is_infinite() or decimal_value < 0: - raise ModelError('invalid metric value {!r} provided:' - ' must be a positive finite float'.format(value)) - return str(decimal_value) - - @classmethod - def validate_label_value(cls, label, value): - # Label values cannot be empty, contain commas or equal signs as those are - # used by add-metric as separators. - if not value: - raise ModelError( - 'metric label {} has an empty value, which is not allowed'.format(label)) - v = str(value) - if re.search('[,=]', v) is not None: - raise ModelError( - 'metric label values must not contain "," or "=": {}={!r}'.format(label, value)) diff --git a/coredns/venv/ops/storage.py b/coredns/venv/ops/storage.py deleted file mode 100644 index ec82d64..0000000 --- a/coredns/venv/ops/storage.py +++ /dev/null @@ -1,318 +0,0 @@ -# Copyright 2019-2020 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from datetime import timedelta -import pickle -import shutil -import subprocess -import sqlite3 -import typing - -import yaml - - -def _run(args, **kw): - cmd = shutil.which(args[0]) - if cmd is None: - raise FileNotFoundError(args[0]) - return subprocess.run([cmd, *args[1:]], **kw) - - -class SQLiteStorage: - - DB_LOCK_TIMEOUT = timedelta(hours=1) - - def __init__(self, filename): - # The isolation_level argument is set to None such that the implicit - # transaction management behavior of the sqlite3 module is disabled. - self._db = sqlite3.connect(str(filename), - isolation_level=None, - timeout=self.DB_LOCK_TIMEOUT.total_seconds()) - self._setup() - - def _setup(self): - # Make sure that the database is locked until the connection is closed, - # not until the transaction ends. - self._db.execute("PRAGMA locking_mode=EXCLUSIVE") - c = self._db.execute("BEGIN") - c.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='snapshot'") - if c.fetchone()[0] == 0: - # Keep in mind what might happen if the process dies somewhere below. - # The system must not be rendered permanently broken by that. - self._db.execute("CREATE TABLE snapshot (handle TEXT PRIMARY KEY, data BLOB)") - self._db.execute(''' - CREATE TABLE notice ( - sequence INTEGER PRIMARY KEY AUTOINCREMENT, - event_path TEXT, - observer_path TEXT, - method_name TEXT) - ''') - self._db.commit() - - def close(self): - self._db.close() - - def commit(self): - self._db.commit() - - # There's commit but no rollback. For abort to be supported, we'll need logic that - # can rollback decisions made by third-party code in terms of the internal state - # of objects that have been snapshotted, and hooks to let them know about it and - # take the needed actions to undo their logic until the last snapshot. - # This is doable but will increase significantly the chances for mistakes. - - def save_snapshot(self, handle_path: str, snapshot_data: typing.Any) -> None: - """Part of the Storage API, persist a snapshot data under the given handle. - - Args: - handle_path: The string identifying the snapshot. - snapshot_data: The data to be persisted. (as returned by Object.snapshot()). This - might be a dict/tuple/int, but must only contain 'simple' python types. - """ - # Use pickle for serialization, so the value remains portable. - raw_data = pickle.dumps(snapshot_data) - self._db.execute("REPLACE INTO snapshot VALUES (?, ?)", (handle_path, raw_data)) - - def load_snapshot(self, handle_path: str) -> typing.Any: - """Part of the Storage API, retrieve a snapshot that was previously saved. - - Args: - handle_path: The string identifying the snapshot. - Raises: - NoSnapshotError: if there is no snapshot for the given handle_path. - """ - c = self._db.cursor() - c.execute("SELECT data FROM snapshot WHERE handle=?", (handle_path,)) - row = c.fetchone() - if row: - return pickle.loads(row[0]) - raise NoSnapshotError(handle_path) - - def drop_snapshot(self, handle_path: str): - """Part of the Storage API, remove a snapshot that was previously saved. - - Dropping a snapshot that doesn't exist is treated as a no-op. - """ - self._db.execute("DELETE FROM snapshot WHERE handle=?", (handle_path,)) - - def list_snapshots(self) -> typing.Generator[str, None, None]: - """Return the name of all snapshots that are currently saved.""" - c = self._db.cursor() - c.execute("SELECT handle FROM snapshot") - while True: - rows = c.fetchmany() - if not rows: - break - for row in rows: - yield row[0] - - def save_notice(self, event_path: str, observer_path: str, method_name: str) -> None: - """Part of the Storage API, record an notice (event and observer)""" - self._db.execute('INSERT INTO notice VALUES (NULL, ?, ?, ?)', - (event_path, observer_path, method_name)) - - def drop_notice(self, event_path: str, observer_path: str, method_name: str) -> None: - """Part of the Storage API, remove a notice that was previously recorded.""" - self._db.execute(''' - DELETE FROM notice - WHERE event_path=? - AND observer_path=? - AND method_name=? - ''', (event_path, observer_path, method_name)) - - def notices(self, event_path: typing.Optional[str]) ->\ - typing.Generator[typing.Tuple[str, str, str], None, None]: - """Part of the Storage API, return all notices that begin with event_path. - - Args: - event_path: If supplied, will only yield events that match event_path. If not - supplied (or None/'') will return all events. - Returns: - Iterable of (event_path, observer_path, method_name) tuples - """ - if event_path: - c = self._db.execute(''' - SELECT event_path, observer_path, method_name - FROM notice - WHERE event_path=? - ORDER BY sequence - ''', (event_path,)) - else: - c = self._db.execute(''' - SELECT event_path, observer_path, method_name - FROM notice - ORDER BY sequence - ''') - while True: - rows = c.fetchmany() - if not rows: - break - for row in rows: - yield tuple(row) - - -class JujuStorage: - """"Storing the content tracked by the Framework in Juju. - - This uses :class:`_JujuStorageBackend` to interact with state-get/state-set - as the way to store state for the framework and for components. - """ - - NOTICE_KEY = "#notices#" - - def __init__(self, backend: '_JujuStorageBackend' = None): - self._backend = backend - if backend is None: - self._backend = _JujuStorageBackend() - - def close(self): - return - - def commit(self): - return - - def save_snapshot(self, handle_path: str, snapshot_data: typing.Any) -> None: - self._backend.set(handle_path, snapshot_data) - - def load_snapshot(self, handle_path): - try: - content = self._backend.get(handle_path) - except KeyError: - raise NoSnapshotError(handle_path) - return content - - def drop_snapshot(self, handle_path): - self._backend.delete(handle_path) - - def save_notice(self, event_path: str, observer_path: str, method_name: str): - notice_list = self._load_notice_list() - notice_list.append([event_path, observer_path, method_name]) - self._save_notice_list(notice_list) - - def drop_notice(self, event_path: str, observer_path: str, method_name: str): - notice_list = self._load_notice_list() - notice_list.remove([event_path, observer_path, method_name]) - self._save_notice_list(notice_list) - - def notices(self, event_path: str): - notice_list = self._load_notice_list() - for row in notice_list: - if row[0] != event_path: - continue - yield tuple(row) - - def _load_notice_list(self) -> typing.List[typing.Tuple[str]]: - try: - notice_list = self._backend.get(self.NOTICE_KEY) - except KeyError: - return [] - if notice_list is None: - return [] - return notice_list - - def _save_notice_list(self, notices: typing.List[typing.Tuple[str]]) -> None: - self._backend.set(self.NOTICE_KEY, notices) - - -class _SimpleLoader(getattr(yaml, 'CSafeLoader', yaml.SafeLoader)): - """Handle a couple basic python types. - - yaml.SafeLoader can handle all the basic int/float/dict/set/etc that we want. The only one - that it *doesn't* handle is tuples. We don't want to support arbitrary types, so we just - subclass SafeLoader and add tuples back in. - """ - # Taken from the example at: - # https://stackoverflow.com/questions/9169025/how-can-i-add-a-python-tuple-to-a-yaml-file-using-pyyaml - - construct_python_tuple = yaml.Loader.construct_python_tuple - - -_SimpleLoader.add_constructor( - u'tag:yaml.org,2002:python/tuple', - _SimpleLoader.construct_python_tuple) - - -class _SimpleDumper(getattr(yaml, 'CSafeDumper', yaml.SafeDumper)): - """Add types supported by 'marshal' - - YAML can support arbitrary types, but that is generally considered unsafe (like pickle). So - we want to only support dumping out types that are safe to load. - """ - - -_SimpleDumper.represent_tuple = yaml.Dumper.represent_tuple -_SimpleDumper.add_representer(tuple, _SimpleDumper.represent_tuple) - - -def juju_backend_available() -> bool: - """Check if Juju state storage is available.""" - p = shutil.which('state-get') - return p is not None - - -class _JujuStorageBackend: - """Implements the interface from the Operator framework to Juju's state-get/set/etc.""" - - def set(self, key: str, value: typing.Any) -> None: - """Set a key to a given value. - - Args: - key: The string key that will be used to find the value later - value: Arbitrary content that will be returned by get(). - Raises: - CalledProcessError: if 'state-set' returns an error code. - """ - # default_flow_style=None means that it can use Block for - # complex types (types that have nested types) but use flow - # for simple types (like an array). Not all versions of PyYAML - # have the same default style. - encoded_value = yaml.dump(value, Dumper=_SimpleDumper, default_flow_style=None) - content = yaml.dump( - {key: encoded_value}, encoding='utf8', default_style='|', - default_flow_style=False, - Dumper=_SimpleDumper) - _run(["state-set", "--file", "-"], input=content, check=True) - - def get(self, key: str) -> typing.Any: - """Get the bytes value associated with a given key. - - Args: - key: The string key that will be used to find the value - Raises: - CalledProcessError: if 'state-get' returns an error code. - """ - # We don't capture stderr here so it can end up in debug logs. - p = _run(["state-get", key], stdout=subprocess.PIPE, check=True, universal_newlines=True) - if p.stdout == '' or p.stdout == '\n': - raise KeyError(key) - return yaml.load(p.stdout, Loader=_SimpleLoader) - - def delete(self, key: str) -> None: - """Remove a key from being tracked. - - Args: - key: The key to stop storing - Raises: - CalledProcessError: if 'state-delete' returns an error code. - """ - _run(["state-delete", key], check=True) - - -class NoSnapshotError(Exception): - - def __init__(self, handle_path): - self.handle_path = handle_path - - def __str__(self): - return 'no snapshot data found for {} object'.format(self.handle_path) diff --git a/coredns/venv/ops/testing.py b/coredns/venv/ops/testing.py deleted file mode 100644 index 416dced..0000000 --- a/coredns/venv/ops/testing.py +++ /dev/null @@ -1,818 +0,0 @@ -# Copyright 2020 Canonical Ltd. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import inspect -import pathlib -import random -import tempfile -import typing -import yaml -from contextlib import contextmanager -from textwrap import dedent - -from ops import ( - charm, - framework, - model, - storage, -) - - -# OptionalYAML is something like metadata.yaml or actions.yaml. You can -# pass in a file-like object or the string directly. -OptionalYAML = typing.Optional[typing.Union[str, typing.TextIO]] - - -# noinspection PyProtectedMember -class Harness: - """This class represents a way to build up the model that will drive a test suite. - - The model that is created is from the viewpoint of the charm that you are testing. - - Example:: - - harness = Harness(MyCharm) - # Do initial setup here - relation_id = harness.add_relation('db', 'postgresql') - # Now instantiate the charm to see events as the model changes - harness.begin() - harness.add_relation_unit(relation_id, 'postgresql/0') - harness.update_relation_data(relation_id, 'postgresql/0', {'key': 'val'}) - # Check that charm has properly handled the relation_joined event for postgresql/0 - self.assertEqual(harness.charm. ...) - - Args: - charm_cls: The Charm class that you'll be testing. - meta: charm.CharmBase is a A string or file-like object containing the contents of - metadata.yaml. If not supplied, we will look for a 'metadata.yaml' file in the - parent directory of the Charm, and if not found fall back to a trivial - 'name: test-charm' metadata. - actions: A string or file-like object containing the contents of - actions.yaml. If not supplied, we will look for a 'actions.yaml' file in the - parent directory of the Charm. - config: A string or file-like object containing the contents of - config.yaml. If not supplied, we will look for a 'config.yaml' file in the - parent directory of the Charm. - """ - - def __init__( - self, - charm_cls: typing.Type[charm.CharmBase], - *, - meta: OptionalYAML = None, - actions: OptionalYAML = None, - config: OptionalYAML = None): - self._charm_cls = charm_cls - self._charm = None - self._charm_dir = 'no-disk-path' # this may be updated by _create_meta - self._meta = self._create_meta(meta, actions) - self._unit_name = self._meta.name + '/0' - self._framework = None - self._hooks_enabled = True - self._relation_id_counter = 0 - self._backend = _TestingModelBackend(self._unit_name, self._meta) - self._model = model.Model(self._meta, self._backend) - self._storage = storage.SQLiteStorage(':memory:') - self._oci_resources = {} - self._framework = framework.Framework( - self._storage, self._charm_dir, self._meta, self._model) - self._update_config(key_values=self._load_config_defaults(config)) - - @property - def charm(self) -> charm.CharmBase: - """Return the instance of the charm class that was passed to __init__. - - Note that the Charm is not instantiated until you have called - :meth:`.begin()`. - """ - return self._charm - - @property - def model(self) -> model.Model: - """Return the :class:`~ops.model.Model` that is being driven by this Harness.""" - return self._model - - @property - def framework(self) -> framework.Framework: - """Return the Framework that is being driven by this Harness.""" - return self._framework - - def begin(self) -> None: - """Instantiate the Charm and start handling events. - - Before calling :meth:`.begin`(), there is no Charm instance, so changes to the Model won't - emit events. You must call :meth:`.begin` before :attr:`.charm` is valid. - """ - if self._charm is not None: - raise RuntimeError('cannot call the begin method on the harness more than once') - - # The Framework adds attributes to class objects for events, etc. As such, we can't re-use - # the original class against multiple Frameworks. So create a locally defined class - # and register it. - # TODO: jam 2020-03-16 We are looking to changes this to Instance attributes instead of - # Class attributes which should clean up this ugliness. The API can stay the same - class TestEvents(self._charm_cls.on.__class__): - pass - - TestEvents.__name__ = self._charm_cls.on.__class__.__name__ - - class TestCharm(self._charm_cls): - on = TestEvents() - - # Note: jam 2020-03-01 This is so that errors in testing say MyCharm has no attribute foo, - # rather than TestCharm has no attribute foo. - TestCharm.__name__ = self._charm_cls.__name__ - self._charm = TestCharm(self._framework) - - def begin_with_initial_hooks(self) -> None: - """Called when you want the Harness to fire the same hooks that Juju would fire at startup. - - This triggers install, relation-created, config-changed, start, and any relation-joined - hooks. Based on what relations have been defined before you called begin(). - Note that all of these are fired before returning control to the test suite, so if you - want to introspect what happens at each step, you need to fire them directly - (eg Charm.on.install.emit()). - - To use this with all the normal hooks, you should instantiate the harness, setup any - relations that you want active when the charm starts, and then call this method. - - Example:: - - harness = Harness(MyCharm) - # Do initial setup here - relation_id = harness.add_relation('db', 'postgresql') - harness.add_relation_unit(relation_id, 'postgresql/0') - harness.update_relation_data(relation_id, 'postgresql/0', {'key': 'val'}) - harness.set_leader(True) - harness.update_config({'initial': 'config'}) - harness.begin_with_initial_hooks() - # This will cause - # install, db-relation-created('postgresql'), leader-elected, config-changed, start - # db-relation-joined('postrgesql/0'), db-relation-changed('postgresql/0') - # To be fired. - """ - self.begin() - # TODO: jam 2020-08-03 This should also handle storage-attached hooks once we have support - # for dealing with storage. - self._charm.on.install.emit() - # Juju itself iterates what relation to fire based on a map[int]relation, so it doesn't - # guarantee a stable ordering between relation events. It *does* give a stable ordering - # of joined units for a given relation. - items = list(self._meta.relations.items()) - random.shuffle(items) - this_app_name = self._meta.name - for relname, rel_meta in items: - if rel_meta.role == charm.RelationRole.peer: - # If the user has directly added a relation, leave it be, but otherwise ensure - # that peer relations are always established at before leader-elected. - rel_ids = self._backend._relation_ids_map.get(relname) - if rel_ids is None: - self.add_relation(relname, self._meta.name) - else: - random.shuffle(rel_ids) - for rel_id in rel_ids: - self._emit_relation_created(relname, rel_id, this_app_name) - else: - rel_ids = self._backend._relation_ids_map.get(relname, []) - random.shuffle(rel_ids) - for rel_id in rel_ids: - app_name = self._backend._relation_app_and_units[rel_id]["app"] - self._emit_relation_created(relname, rel_id, app_name) - if self._backend._is_leader: - self._charm.on.leader_elected.emit() - else: - self._charm.on.leader_settings_changed.emit() - self._charm.on.config_changed.emit() - self._charm.on.start.emit() - all_ids = list(self._backend._relation_names.items()) - random.shuffle(all_ids) - for rel_id, rel_name in all_ids: - rel_app_and_units = self._backend._relation_app_and_units[rel_id] - app_name = rel_app_and_units["app"] - # Note: Juju *does* fire relation events for a given relation in the sorted order of - # the unit names. It also always fires relation-changed immediately after - # relation-joined for the same unit. - # Juju only fires relation-changed (app) if there is data for the related application - relation = self._model.get_relation(rel_name, rel_id) - if self._backend._relation_data[rel_id].get(app_name): - app = self._model.get_app(app_name) - self._charm.on[rel_name].relation_changed.emit( - relation, app, None) - for unit_name in sorted(rel_app_and_units["units"]): - remote_unit = self._model.get_unit(unit_name) - self._charm.on[rel_name].relation_joined.emit( - relation, remote_unit.app, remote_unit) - self._charm.on[rel_name].relation_changed.emit( - relation, remote_unit.app, remote_unit) - - def cleanup(self) -> None: - """Called by your test infrastructure to cleanup any temporary directories/files/etc. - - Currently this only needs to be called if you test with resources. But it is reasonable - to always include a `testcase.addCleanup(harness.cleanup)` just in case. - """ - self._backend._cleanup() - - def _create_meta(self, charm_metadata, action_metadata): - """Create a CharmMeta object. - - Handle the cases where a user doesn't supply explicit metadata snippets. - """ - filename = inspect.getfile(self._charm_cls) - charm_dir = pathlib.Path(filename).parents[1] - - if charm_metadata is None: - metadata_path = charm_dir / 'metadata.yaml' - if metadata_path.is_file(): - charm_metadata = metadata_path.read_text() - self._charm_dir = charm_dir - else: - # The simplest of metadata that the framework can support - charm_metadata = 'name: test-charm' - elif isinstance(charm_metadata, str): - charm_metadata = dedent(charm_metadata) - - if action_metadata is None: - actions_path = charm_dir / 'actions.yaml' - if actions_path.is_file(): - action_metadata = actions_path.read_text() - self._charm_dir = charm_dir - elif isinstance(action_metadata, str): - action_metadata = dedent(action_metadata) - - return charm.CharmMeta.from_yaml(charm_metadata, action_metadata) - - def _load_config_defaults(self, charm_config): - """Load default values from config.yaml - - Handle the case where a user doesn't supply explicit config snippets. - """ - filename = inspect.getfile(self._charm_cls) - charm_dir = pathlib.Path(filename).parents[1] - - if charm_config is None: - config_path = charm_dir / 'config.yaml' - if config_path.is_file(): - charm_config = config_path.read_text() - self._charm_dir = charm_dir - else: - # The simplest of config that the framework can support - charm_config = '{}' - elif isinstance(charm_config, str): - charm_config = dedent(charm_config) - charm_config = yaml.load(charm_config, Loader=yaml.SafeLoader) - charm_config = charm_config.get('options', {}) - return {key: value['default'] for key, value in charm_config.items() - if 'default' in value} - - def add_oci_resource(self, resource_name: str, - contents: typing.Mapping[str, str] = None) -> None: - """Add oci resources to the backend. - - This will register an oci resource and create a temporary file for processing metadata - about the resource. A default set of values will be used for all the file contents - unless a specific contents dict is provided. - - Args: - resource_name: Name of the resource to add custom contents to. - contents: Optional custom dict to write for the named resource. - """ - if not contents: - contents = {'registrypath': 'registrypath', - 'username': 'username', - 'password': 'password', - } - if resource_name not in self._meta.resources.keys(): - raise RuntimeError('Resource {} is not a defined resources'.format(resource_name)) - if self._meta.resources[resource_name].type != "oci-image": - raise RuntimeError('Resource {} is not an OCI Image'.format(resource_name)) - - as_yaml = yaml.dump(contents, Dumper=yaml.SafeDumper) - self._backend._resources_map[resource_name] = ('contents.yaml', as_yaml) - - def add_resource(self, resource_name: str, content: typing.AnyStr) -> None: - """Add content for a resource to the backend. - - This will register the content, so that a call to `Model.resources.fetch(resource_name)` - will return a path to a file containing that content. - - Args: - resource_name: The name of the resource being added - contents: Either string or bytes content, which will be the content of the filename - returned by resource-get. If contents is a string, it will be encoded in utf-8 - """ - if resource_name not in self._meta.resources.keys(): - raise RuntimeError('Resource {} is not a defined resources'.format(resource_name)) - record = self._meta.resources[resource_name] - if record.type != "file": - raise RuntimeError( - 'Resource {} is not a file, but actually {}'.format(resource_name, record.type)) - filename = record.filename - if filename is None: - filename = resource_name - - self._backend._resources_map[resource_name] = (filename, content) - - def populate_oci_resources(self) -> None: - """Populate all OCI resources.""" - for name, data in self._meta.resources.items(): - if data.type == "oci-image": - self.add_oci_resource(name) - - def disable_hooks(self) -> None: - """Stop emitting hook events when the model changes. - - This can be used by developers to stop changes to the model from emitting events that - the charm will react to. Call :meth:`.enable_hooks` - to re-enable them. - """ - self._hooks_enabled = False - - def enable_hooks(self) -> None: - """Re-enable hook events from charm.on when the model is changed. - - By default hook events are enabled once you call :meth:`.begin`, - but if you have used :meth:`.disable_hooks`, this can be used to - enable them again. - """ - self._hooks_enabled = True - - @contextmanager - def hooks_disabled(self): - """A context manager to run code with hooks disabled. - - Example:: - - with harness.hooks_disabled(): - # things in here don't fire events - harness.set_leader(True) - harness.update_config(unset=['foo', 'bar']) - # things here will again fire events - """ - self.disable_hooks() - try: - yield None - finally: - self.enable_hooks() - - def _next_relation_id(self): - rel_id = self._relation_id_counter - self._relation_id_counter += 1 - return rel_id - - def add_relation(self, relation_name: str, remote_app: str) -> int: - """Declare that there is a new relation between this app and `remote_app`. - - Args: - relation_name: The relation on Charm that is being related to - remote_app: The name of the application that is being related to - - Return: - The relation_id created by this add_relation. - """ - rel_id = self._next_relation_id() - self._backend._relation_ids_map.setdefault(relation_name, []).append(rel_id) - self._backend._relation_names[rel_id] = relation_name - self._backend._relation_list_map[rel_id] = [] - self._backend._relation_data[rel_id] = { - remote_app: {}, - self._backend.unit_name: {}, - self._backend.app_name: {}, - } - self._backend._relation_app_and_units[rel_id] = { - "app": remote_app, - "units": [], - } - # Reload the relation_ids list - if self._model is not None: - self._model.relations._invalidate(relation_name) - self._emit_relation_created(relation_name, rel_id, remote_app) - return rel_id - - def _emit_relation_created(self, relation_name: str, relation_id: int, - remote_app: str) -> None: - """Trigger relation-created for a given relation with a given remote application.""" - if self._charm is None or not self._hooks_enabled: - return - if self._charm is None or not self._hooks_enabled: - return - relation = self._model.get_relation(relation_name, relation_id) - app = self._model.get_app(remote_app) - self._charm.on[relation_name].relation_created.emit( - relation, app) - - def add_relation_unit(self, relation_id: int, remote_unit_name: str) -> None: - """Add a new unit to a relation. - - Example:: - - rel_id = harness.add_relation('db', 'postgresql') - harness.add_relation_unit(rel_id, 'postgresql/0') - - This will trigger a `relation_joined` event. This would naturally be - followed by a `relation_changed` event, which you can trigger with - :meth:`.update_relation_data`. This separation is artificial in the - sense that Juju will always fire the two, but is intended to make - testing relations and their data bags slightly more natural. - - Args: - relation_id: The integer relation identifier (as returned by add_relation). - remote_unit_name: A string representing the remote unit that is being added. - Return: - None - """ - self._backend._relation_list_map[relation_id].append(remote_unit_name) - self._backend._relation_data[relation_id][remote_unit_name] = {} - # TODO: jam 2020-08-03 This is where we could assert that the unit name matches the - # application name (eg you don't have a relation to 'foo' but add units of 'bar/0' - self._backend._relation_app_and_units[relation_id]["units"].append(remote_unit_name) - relation_name = self._backend._relation_names[relation_id] - # Make sure that the Model reloads the relation_list for this relation_id, as well as - # reloading the relation data for this unit. - if self._model is not None: - remote_unit = self._model.get_unit(remote_unit_name) - relation = self._model.get_relation(relation_name, relation_id) - unit_cache = relation.data.get(remote_unit, None) - if unit_cache is not None: - unit_cache._invalidate() - self._model.relations._invalidate(relation_name) - if self._charm is None or not self._hooks_enabled: - return - self._charm.on[relation_name].relation_joined.emit( - relation, remote_unit.app, remote_unit) - - def get_relation_data(self, relation_id: int, app_or_unit: str) -> typing.Mapping: - """Get the relation data bucket for a single app or unit in a given relation. - - This ignores all of the safety checks of who can and can't see data in relations (eg, - non-leaders can't read their own application's relation data because there are no events - that keep that data up-to-date for the unit). - - Args: - relation_id: The relation whose content we want to look at. - app_or_unit: The name of the application or unit whose data we want to read - Return: - a dict containing the relation data for `app_or_unit` or None. - Raises: - KeyError: if relation_id doesn't exist - """ - return self._backend._relation_data[relation_id].get(app_or_unit, None) - - def get_pod_spec(self) -> (typing.Mapping, typing.Mapping): - """Return the content of the pod spec as last set by the charm. - - This returns both the pod spec and any k8s_resources that were supplied. - See the signature of Model.pod.set_spec - """ - return self._backend._pod_spec - - def get_workload_version(self) -> str: - """Read the workload version that was set by the unit.""" - return self._backend._workload_version - - def set_model_name(self, name: str) -> None: - """Set the name of the Model that this is representing. - - This cannot be called once begin() has been called. But it lets you set the value that - will be returned by Model.name. - """ - if self._charm is not None: - raise RuntimeError('cannot set the Model name after begin()') - self._backend.model_name = name - - def update_relation_data( - self, - relation_id: int, - app_or_unit: str, - key_values: typing.Mapping, - ) -> None: - """Update the relation data for a given unit or application in a given relation. - - This also triggers the `relation_changed` event for this relation_id. - - Args: - relation_id: The integer relation_id representing this relation. - app_or_unit: The unit or application name that is being updated. - This can be the local or remote application. - key_values: Each key/value will be updated in the relation data. - """ - relation_name = self._backend._relation_names[relation_id] - relation = self._model.get_relation(relation_name, relation_id) - if '/' in app_or_unit: - entity = self._model.get_unit(app_or_unit) - else: - entity = self._model.get_app(app_or_unit) - rel_data = relation.data.get(entity, None) - if rel_data is not None: - # rel_data may have cached now-stale data, so _invalidate() it. - # Note, this won't cause the data to be loaded if it wasn't already. - rel_data._invalidate() - - new_values = self._backend._relation_data[relation_id][app_or_unit].copy() - for k, v in key_values.items(): - if v == '': - new_values.pop(k, None) - else: - new_values[k] = v - self._backend._relation_data[relation_id][app_or_unit] = new_values - - if app_or_unit == self._model.unit.name: - # No events for our own unit - return - if app_or_unit == self._model.app.name: - # updating our own app only generates an event if it is a peer relation and we - # aren't the leader - is_peer = self._meta.relations[relation_name].role.is_peer() - if not is_peer: - return - if self._model.unit.is_leader(): - return - self._emit_relation_changed(relation_id, app_or_unit) - - def _emit_relation_changed(self, relation_id, app_or_unit): - if self._charm is None or not self._hooks_enabled: - return - rel_name = self._backend._relation_names[relation_id] - relation = self.model.get_relation(rel_name, relation_id) - if '/' in app_or_unit: - app_name = app_or_unit.split('/')[0] - unit_name = app_or_unit - app = self.model.get_app(app_name) - unit = self.model.get_unit(unit_name) - args = (relation, app, unit) - else: - app_name = app_or_unit - app = self.model.get_app(app_name) - args = (relation, app) - self._charm.on[rel_name].relation_changed.emit(*args) - - def _update_config( - self, - key_values: typing.Mapping[str, str] = None, - unset: typing.Iterable[str] = (), - ) -> None: - """Update the config as seen by the charm. - - This will *not* trigger a `config_changed` event, and is intended for internal use. - - Note that the `key_values` mapping will only add or update configuration items. - To remove existing ones, see the `unset` parameter. - - Args: - key_values: A Mapping of key:value pairs to update in config. - unset: An iterable of keys to remove from Config. (Note that this does - not currently reset the config values to the default defined in config.yaml.) - """ - # NOTE: jam 2020-03-01 Note that this sort of works "by accident". Config - # is a LazyMapping, but its _load returns a dict and this method mutates - # the dict that Config is caching. Arguably we should be doing some sort - # of charm.framework.model.config._invalidate() - config = self._backend._config - if key_values is not None: - for key, value in key_values.items(): - config[key] = value - for key in unset: - config.pop(key, None) - - def update_config( - self, - key_values: typing.Mapping[str, str] = None, - unset: typing.Iterable[str] = (), - ) -> None: - """Update the config as seen by the charm. - - This will trigger a `config_changed` event. - - Note that the `key_values` mapping will only add or update configuration items. - To remove existing ones, see the `unset` parameter. - - Args: - key_values: A Mapping of key:value pairs to update in config. - unset: An iterable of keys to remove from Config. (Note that this does - not currently reset the config values to the default defined in config.yaml.) - """ - self._update_config(key_values, unset) - if self._charm is None or not self._hooks_enabled: - return - self._charm.on.config_changed.emit() - - def set_leader(self, is_leader: bool = True) -> None: - """Set whether this unit is the leader or not. - - If this charm becomes a leader then `leader_elected` will be triggered. - - Args: - is_leader: True/False as to whether this unit is the leader. - """ - was_leader = self._backend._is_leader - self._backend._is_leader = is_leader - # Note: jam 2020-03-01 currently is_leader is cached at the ModelBackend level, not in - # the Model objects, so this automatically gets noticed. - if is_leader and not was_leader and self._charm is not None and self._hooks_enabled: - self._charm.on.leader_elected.emit() - - def _get_backend_calls(self, reset: bool = True) -> list: - """Return the calls that we have made to the TestingModelBackend. - - This is useful mostly for testing the framework itself, so that we can assert that we - do/don't trigger extra calls. - - Args: - reset: If True, reset the calls list back to empty, if false, the call list is - preserved. - Return: - ``[(call1, args...), (call2, args...)]`` - """ - calls = self._backend._calls.copy() - if reset: - self._backend._calls.clear() - return calls - - -def _record_calls(cls): - """Replace methods on cls with methods that record that they have been called. - - Iterate all attributes of cls, and for public methods, replace them with a wrapped method - that records the method called along with the arguments and keyword arguments. - """ - for meth_name, orig_method in cls.__dict__.items(): - if meth_name.startswith('_'): - continue - - def decorator(orig_method): - def wrapped(self, *args, **kwargs): - full_args = (orig_method.__name__,) + args - if kwargs: - full_args = full_args + (kwargs,) - self._calls.append(full_args) - return orig_method(self, *args, **kwargs) - return wrapped - - setattr(cls, meth_name, decorator(orig_method)) - return cls - - -class _ResourceEntry: - """Tracks the contents of a Resource.""" - - def __init__(self, resource_name): - self.name = resource_name - - -@_record_calls -class _TestingModelBackend: - """This conforms to the interface for ModelBackend but provides canned data. - - DO NOT use this class directly, it is used by `Harness`_ to drive the model. - `Harness`_ is responsible for maintaining the internal consistency of the values here, - as the only public methods of this type are for implementing ModelBackend. - """ - - def __init__(self, unit_name, meta): - self.unit_name = unit_name - self.app_name = self.unit_name.split('/')[0] - self.model_name = None - self._calls = [] - self._meta = meta - self._is_leader = None - self._relation_ids_map = {} # relation name to [relation_ids,...] - self._relation_names = {} # reverse map from relation_id to relation_name - self._relation_list_map = {} # relation_id: [unit_name,...] - self._relation_data = {} # {relation_id: {name: data}} - # {relation_id: {"app": app_name, "units": ["app/0",...]} - self._relation_app_and_units = {} - self._config = {} - self._is_leader = False - self._resources_map = {} # {resource_name: resource_content} - self._pod_spec = None - self._app_status = {'status': 'unknown', 'message': ''} - self._unit_status = {'status': 'maintenance', 'message': ''} - self._workload_version = None - self._resource_dir = None - - def _cleanup(self): - if self._resource_dir is not None: - self._resource_dir.cleanup() - self._resource_dir = None - - def _get_resource_dir(self) -> pathlib.Path: - if self._resource_dir is None: - # In actual Juju, the resource path for a charm's resource is - # $AGENT_DIR/resources/$RESOURCE_NAME/$RESOURCE_FILENAME - # However, charms shouldn't depend on this. - self._resource_dir = tempfile.TemporaryDirectory(prefix='tmp-ops-test-resource-') - return pathlib.Path(self._resource_dir.name) - - def relation_ids(self, relation_name): - try: - return self._relation_ids_map[relation_name] - except KeyError as e: - if relation_name not in self._meta.relations: - raise model.ModelError('{} is not a known relation'.format(relation_name)) from e - return [] - - def relation_list(self, relation_id): - try: - return self._relation_list_map[relation_id] - except KeyError as e: - raise model.RelationNotFoundError from e - - def relation_get(self, relation_id, member_name, is_app): - if is_app and '/' in member_name: - member_name = member_name.split('/')[0] - if relation_id not in self._relation_data: - raise model.RelationNotFoundError() - return self._relation_data[relation_id][member_name].copy() - - def relation_set(self, relation_id, key, value, is_app): - relation = self._relation_data[relation_id] - if is_app: - bucket_key = self.app_name - else: - bucket_key = self.unit_name - if bucket_key not in relation: - relation[bucket_key] = {} - bucket = relation[bucket_key] - if value == '': - bucket.pop(key, None) - else: - bucket[key] = value - - def config_get(self): - return self._config - - def is_leader(self): - return self._is_leader - - def application_version_set(self, version): - self._workload_version = version - - def resource_get(self, resource_name): - if resource_name not in self._resources_map: - raise model.ModelError( - "ERROR could not download resource: HTTP request failed: " - "Get https://.../units/unit-{}/resources/{}: resource#{}/{} not found".format( - self.unit_name.replace('/', '-'), resource_name, self.app_name, resource_name - )) - filename, contents = self._resources_map[resource_name] - resource_dir = self._get_resource_dir() - resource_filename = resource_dir / resource_name / filename - if not resource_filename.exists(): - if isinstance(contents, bytes): - mode = 'wb' - else: - mode = 'wt' - resource_filename.parent.mkdir(exist_ok=True) - with resource_filename.open(mode=mode) as resource_file: - resource_file.write(contents) - return resource_filename - - def pod_spec_set(self, spec, k8s_resources): - self._pod_spec = (spec, k8s_resources) - - def status_get(self, *, is_app=False): - if is_app: - return self._app_status - else: - return self._unit_status - - def status_set(self, status, message='', *, is_app=False): - if is_app: - self._app_status = {'status': status, 'message': message} - else: - self._unit_status = {'status': status, 'message': message} - - def storage_list(self, name): - raise NotImplementedError(self.storage_list) - - def storage_get(self, storage_name_id, attribute): - raise NotImplementedError(self.storage_get) - - def storage_add(self, name, count=1): - raise NotImplementedError(self.storage_add) - - def action_get(self): - raise NotImplementedError(self.action_get) - - def action_set(self, results): - raise NotImplementedError(self.action_set) - - def action_log(self, message): - raise NotImplementedError(self.action_log) - - def action_fail(self, message=''): - raise NotImplementedError(self.action_fail) - - def network_get(self, endpoint_name, relation_id=None): - raise NotImplementedError(self.network_get) diff --git a/coredns/venv/ops/version.py b/coredns/venv/ops/version.py deleted file mode 100644 index 9ecc830..0000000 --- a/coredns/venv/ops/version.py +++ /dev/null @@ -1,3 +0,0 @@ -# this is a generated file - -version = '0.10.0' diff --git a/coredns/venv/yaml/__init__.py b/coredns/venv/yaml/__init__.py deleted file mode 100644 index 13d687c..0000000 --- a/coredns/venv/yaml/__init__.py +++ /dev/null @@ -1,427 +0,0 @@ - -from .error import * - -from .tokens import * -from .events import * -from .nodes import * - -from .loader import * -from .dumper import * - -__version__ = '5.3.1' -try: - from .cyaml import * - __with_libyaml__ = True -except ImportError: - __with_libyaml__ = False - -import io - -#------------------------------------------------------------------------------ -# Warnings control -#------------------------------------------------------------------------------ - -# 'Global' warnings state: -_warnings_enabled = { - 'YAMLLoadWarning': True, -} - -# Get or set global warnings' state -def warnings(settings=None): - if settings is None: - return _warnings_enabled - - if type(settings) is dict: - for key in settings: - if key in _warnings_enabled: - _warnings_enabled[key] = settings[key] - -# Warn when load() is called without Loader=... -class YAMLLoadWarning(RuntimeWarning): - pass - -def load_warning(method): - if _warnings_enabled['YAMLLoadWarning'] is False: - return - - import warnings - - message = ( - "calling yaml.%s() without Loader=... is deprecated, as the " - "default Loader is unsafe. Please read " - "https://msg.pyyaml.org/load for full details." - ) % method - - warnings.warn(message, YAMLLoadWarning, stacklevel=3) - -#------------------------------------------------------------------------------ -def scan(stream, Loader=Loader): - """ - Scan a YAML stream and produce scanning tokens. - """ - loader = Loader(stream) - try: - while loader.check_token(): - yield loader.get_token() - finally: - loader.dispose() - -def parse(stream, Loader=Loader): - """ - Parse a YAML stream and produce parsing events. - """ - loader = Loader(stream) - try: - while loader.check_event(): - yield loader.get_event() - finally: - loader.dispose() - -def compose(stream, Loader=Loader): - """ - Parse the first YAML document in a stream - and produce the corresponding representation tree. - """ - loader = Loader(stream) - try: - return loader.get_single_node() - finally: - loader.dispose() - -def compose_all(stream, Loader=Loader): - """ - Parse all YAML documents in a stream - and produce corresponding representation trees. - """ - loader = Loader(stream) - try: - while loader.check_node(): - yield loader.get_node() - finally: - loader.dispose() - -def load(stream, Loader=None): - """ - Parse the first YAML document in a stream - and produce the corresponding Python object. - """ - if Loader is None: - load_warning('load') - Loader = FullLoader - - loader = Loader(stream) - try: - return loader.get_single_data() - finally: - loader.dispose() - -def load_all(stream, Loader=None): - """ - Parse all YAML documents in a stream - and produce corresponding Python objects. - """ - if Loader is None: - load_warning('load_all') - Loader = FullLoader - - loader = Loader(stream) - try: - while loader.check_data(): - yield loader.get_data() - finally: - loader.dispose() - -def full_load(stream): - """ - Parse the first YAML document in a stream - and produce the corresponding Python object. - - Resolve all tags except those known to be - unsafe on untrusted input. - """ - return load(stream, FullLoader) - -def full_load_all(stream): - """ - Parse all YAML documents in a stream - and produce corresponding Python objects. - - Resolve all tags except those known to be - unsafe on untrusted input. - """ - return load_all(stream, FullLoader) - -def safe_load(stream): - """ - Parse the first YAML document in a stream - and produce the corresponding Python object. - - Resolve only basic YAML tags. This is known - to be safe for untrusted input. - """ - return load(stream, SafeLoader) - -def safe_load_all(stream): - """ - Parse all YAML documents in a stream - and produce corresponding Python objects. - - Resolve only basic YAML tags. This is known - to be safe for untrusted input. - """ - return load_all(stream, SafeLoader) - -def unsafe_load(stream): - """ - Parse the first YAML document in a stream - and produce the corresponding Python object. - - Resolve all tags, even those known to be - unsafe on untrusted input. - """ - return load(stream, UnsafeLoader) - -def unsafe_load_all(stream): - """ - Parse all YAML documents in a stream - and produce corresponding Python objects. - - Resolve all tags, even those known to be - unsafe on untrusted input. - """ - return load_all(stream, UnsafeLoader) - -def emit(events, stream=None, Dumper=Dumper, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None): - """ - Emit YAML parsing events into a stream. - If stream is None, return the produced string instead. - """ - getvalue = None - if stream is None: - stream = io.StringIO() - getvalue = stream.getvalue - dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break) - try: - for event in events: - dumper.emit(event) - finally: - dumper.dispose() - if getvalue: - return getvalue() - -def serialize_all(nodes, stream=None, Dumper=Dumper, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None): - """ - Serialize a sequence of representation trees into a YAML stream. - If stream is None, return the produced string instead. - """ - getvalue = None - if stream is None: - if encoding is None: - stream = io.StringIO() - else: - stream = io.BytesIO() - getvalue = stream.getvalue - dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break, - encoding=encoding, version=version, tags=tags, - explicit_start=explicit_start, explicit_end=explicit_end) - try: - dumper.open() - for node in nodes: - dumper.serialize(node) - dumper.close() - finally: - dumper.dispose() - if getvalue: - return getvalue() - -def serialize(node, stream=None, Dumper=Dumper, **kwds): - """ - Serialize a representation tree into a YAML stream. - If stream is None, return the produced string instead. - """ - return serialize_all([node], stream, Dumper=Dumper, **kwds) - -def dump_all(documents, stream=None, Dumper=Dumper, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - """ - Serialize a sequence of Python objects into a YAML stream. - If stream is None, return the produced string instead. - """ - getvalue = None - if stream is None: - if encoding is None: - stream = io.StringIO() - else: - stream = io.BytesIO() - getvalue = stream.getvalue - dumper = Dumper(stream, default_style=default_style, - default_flow_style=default_flow_style, - canonical=canonical, indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break, - encoding=encoding, version=version, tags=tags, - explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys) - try: - dumper.open() - for data in documents: - dumper.represent(data) - dumper.close() - finally: - dumper.dispose() - if getvalue: - return getvalue() - -def dump(data, stream=None, Dumper=Dumper, **kwds): - """ - Serialize a Python object into a YAML stream. - If stream is None, return the produced string instead. - """ - return dump_all([data], stream, Dumper=Dumper, **kwds) - -def safe_dump_all(documents, stream=None, **kwds): - """ - Serialize a sequence of Python objects into a YAML stream. - Produce only basic YAML tags. - If stream is None, return the produced string instead. - """ - return dump_all(documents, stream, Dumper=SafeDumper, **kwds) - -def safe_dump(data, stream=None, **kwds): - """ - Serialize a Python object into a YAML stream. - Produce only basic YAML tags. - If stream is None, return the produced string instead. - """ - return dump_all([data], stream, Dumper=SafeDumper, **kwds) - -def add_implicit_resolver(tag, regexp, first=None, - Loader=None, Dumper=Dumper): - """ - Add an implicit scalar detector. - If an implicit scalar value matches the given regexp, - the corresponding tag is assigned to the scalar. - first is a sequence of possible initial characters or None. - """ - if Loader is None: - loader.Loader.add_implicit_resolver(tag, regexp, first) - loader.FullLoader.add_implicit_resolver(tag, regexp, first) - loader.UnsafeLoader.add_implicit_resolver(tag, regexp, first) - else: - Loader.add_implicit_resolver(tag, regexp, first) - Dumper.add_implicit_resolver(tag, regexp, first) - -def add_path_resolver(tag, path, kind=None, Loader=None, Dumper=Dumper): - """ - Add a path based resolver for the given tag. - A path is a list of keys that forms a path - to a node in the representation tree. - Keys can be string values, integers, or None. - """ - if Loader is None: - loader.Loader.add_path_resolver(tag, path, kind) - loader.FullLoader.add_path_resolver(tag, path, kind) - loader.UnsafeLoader.add_path_resolver(tag, path, kind) - else: - Loader.add_path_resolver(tag, path, kind) - Dumper.add_path_resolver(tag, path, kind) - -def add_constructor(tag, constructor, Loader=None): - """ - Add a constructor for the given tag. - Constructor is a function that accepts a Loader instance - and a node object and produces the corresponding Python object. - """ - if Loader is None: - loader.Loader.add_constructor(tag, constructor) - loader.FullLoader.add_constructor(tag, constructor) - loader.UnsafeLoader.add_constructor(tag, constructor) - else: - Loader.add_constructor(tag, constructor) - -def add_multi_constructor(tag_prefix, multi_constructor, Loader=None): - """ - Add a multi-constructor for the given tag prefix. - Multi-constructor is called for a node if its tag starts with tag_prefix. - Multi-constructor accepts a Loader instance, a tag suffix, - and a node object and produces the corresponding Python object. - """ - if Loader is None: - loader.Loader.add_multi_constructor(tag_prefix, multi_constructor) - loader.FullLoader.add_multi_constructor(tag_prefix, multi_constructor) - loader.UnsafeLoader.add_multi_constructor(tag_prefix, multi_constructor) - else: - Loader.add_multi_constructor(tag_prefix, multi_constructor) - -def add_representer(data_type, representer, Dumper=Dumper): - """ - Add a representer for the given type. - Representer is a function accepting a Dumper instance - and an instance of the given data type - and producing the corresponding representation node. - """ - Dumper.add_representer(data_type, representer) - -def add_multi_representer(data_type, multi_representer, Dumper=Dumper): - """ - Add a representer for the given type. - Multi-representer is a function accepting a Dumper instance - and an instance of the given data type or subtype - and producing the corresponding representation node. - """ - Dumper.add_multi_representer(data_type, multi_representer) - -class YAMLObjectMetaclass(type): - """ - The metaclass for YAMLObject. - """ - def __init__(cls, name, bases, kwds): - super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds) - if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None: - if isinstance(cls.yaml_loader, list): - for loader in cls.yaml_loader: - loader.add_constructor(cls.yaml_tag, cls.from_yaml) - else: - cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml) - - cls.yaml_dumper.add_representer(cls, cls.to_yaml) - -class YAMLObject(metaclass=YAMLObjectMetaclass): - """ - An object that can dump itself to a YAML stream - and load itself from a YAML stream. - """ - - __slots__ = () # no direct instantiation, so allow immutable subclasses - - yaml_loader = [Loader, FullLoader, UnsafeLoader] - yaml_dumper = Dumper - - yaml_tag = None - yaml_flow_style = None - - @classmethod - def from_yaml(cls, loader, node): - """ - Convert a representation node to a Python object. - """ - return loader.construct_yaml_object(node, cls) - - @classmethod - def to_yaml(cls, dumper, data): - """ - Convert a Python object to a representation node. - """ - return dumper.represent_yaml_object(cls.yaml_tag, data, cls, - flow_style=cls.yaml_flow_style) - diff --git a/coredns/venv/yaml/__pycache__/__init__.cpython-38.pyc b/coredns/venv/yaml/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 18da437..0000000 Binary files a/coredns/venv/yaml/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/composer.cpython-38.pyc b/coredns/venv/yaml/__pycache__/composer.cpython-38.pyc deleted file mode 100644 index 483755e..0000000 Binary files a/coredns/venv/yaml/__pycache__/composer.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/constructor.cpython-38.pyc b/coredns/venv/yaml/__pycache__/constructor.cpython-38.pyc deleted file mode 100644 index b0e3021..0000000 Binary files a/coredns/venv/yaml/__pycache__/constructor.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/cyaml.cpython-38.pyc b/coredns/venv/yaml/__pycache__/cyaml.cpython-38.pyc deleted file mode 100644 index 0e4fdf0..0000000 Binary files a/coredns/venv/yaml/__pycache__/cyaml.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/dumper.cpython-38.pyc b/coredns/venv/yaml/__pycache__/dumper.cpython-38.pyc deleted file mode 100644 index 0f2c1e6..0000000 Binary files a/coredns/venv/yaml/__pycache__/dumper.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/emitter.cpython-38.pyc b/coredns/venv/yaml/__pycache__/emitter.cpython-38.pyc deleted file mode 100644 index fbe86dd..0000000 Binary files a/coredns/venv/yaml/__pycache__/emitter.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/error.cpython-38.pyc b/coredns/venv/yaml/__pycache__/error.cpython-38.pyc deleted file mode 100644 index 481c6cf..0000000 Binary files a/coredns/venv/yaml/__pycache__/error.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/events.cpython-38.pyc b/coredns/venv/yaml/__pycache__/events.cpython-38.pyc deleted file mode 100644 index ee8ca1b..0000000 Binary files a/coredns/venv/yaml/__pycache__/events.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/loader.cpython-38.pyc b/coredns/venv/yaml/__pycache__/loader.cpython-38.pyc deleted file mode 100644 index ed7fdcf..0000000 Binary files a/coredns/venv/yaml/__pycache__/loader.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/nodes.cpython-38.pyc b/coredns/venv/yaml/__pycache__/nodes.cpython-38.pyc deleted file mode 100644 index 7766e80..0000000 Binary files a/coredns/venv/yaml/__pycache__/nodes.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/parser.cpython-38.pyc b/coredns/venv/yaml/__pycache__/parser.cpython-38.pyc deleted file mode 100644 index 7047475..0000000 Binary files a/coredns/venv/yaml/__pycache__/parser.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/reader.cpython-38.pyc b/coredns/venv/yaml/__pycache__/reader.cpython-38.pyc deleted file mode 100644 index d1c3b1b..0000000 Binary files a/coredns/venv/yaml/__pycache__/reader.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/representer.cpython-38.pyc b/coredns/venv/yaml/__pycache__/representer.cpython-38.pyc deleted file mode 100644 index c4544e2..0000000 Binary files a/coredns/venv/yaml/__pycache__/representer.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/resolver.cpython-38.pyc b/coredns/venv/yaml/__pycache__/resolver.cpython-38.pyc deleted file mode 100644 index ec45829..0000000 Binary files a/coredns/venv/yaml/__pycache__/resolver.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/scanner.cpython-38.pyc b/coredns/venv/yaml/__pycache__/scanner.cpython-38.pyc deleted file mode 100644 index 70d13ce..0000000 Binary files a/coredns/venv/yaml/__pycache__/scanner.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/serializer.cpython-38.pyc b/coredns/venv/yaml/__pycache__/serializer.cpython-38.pyc deleted file mode 100644 index e4ce884..0000000 Binary files a/coredns/venv/yaml/__pycache__/serializer.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/__pycache__/tokens.cpython-38.pyc b/coredns/venv/yaml/__pycache__/tokens.cpython-38.pyc deleted file mode 100644 index 2ad8c48..0000000 Binary files a/coredns/venv/yaml/__pycache__/tokens.cpython-38.pyc and /dev/null differ diff --git a/coredns/venv/yaml/composer.py b/coredns/venv/yaml/composer.py deleted file mode 100644 index 6d15cb4..0000000 --- a/coredns/venv/yaml/composer.py +++ /dev/null @@ -1,139 +0,0 @@ - -__all__ = ['Composer', 'ComposerError'] - -from .error import MarkedYAMLError -from .events import * -from .nodes import * - -class ComposerError(MarkedYAMLError): - pass - -class Composer: - - def __init__(self): - self.anchors = {} - - def check_node(self): - # Drop the STREAM-START event. - if self.check_event(StreamStartEvent): - self.get_event() - - # If there are more documents available? - return not self.check_event(StreamEndEvent) - - def get_node(self): - # Get the root node of the next document. - if not self.check_event(StreamEndEvent): - return self.compose_document() - - def get_single_node(self): - # Drop the STREAM-START event. - self.get_event() - - # Compose a document if the stream is not empty. - document = None - if not self.check_event(StreamEndEvent): - document = self.compose_document() - - # Ensure that the stream contains no more documents. - if not self.check_event(StreamEndEvent): - event = self.get_event() - raise ComposerError("expected a single document in the stream", - document.start_mark, "but found another document", - event.start_mark) - - # Drop the STREAM-END event. - self.get_event() - - return document - - def compose_document(self): - # Drop the DOCUMENT-START event. - self.get_event() - - # Compose the root node. - node = self.compose_node(None, None) - - # Drop the DOCUMENT-END event. - self.get_event() - - self.anchors = {} - return node - - def compose_node(self, parent, index): - if self.check_event(AliasEvent): - event = self.get_event() - anchor = event.anchor - if anchor not in self.anchors: - raise ComposerError(None, None, "found undefined alias %r" - % anchor, event.start_mark) - return self.anchors[anchor] - event = self.peek_event() - anchor = event.anchor - if anchor is not None: - if anchor in self.anchors: - raise ComposerError("found duplicate anchor %r; first occurrence" - % anchor, self.anchors[anchor].start_mark, - "second occurrence", event.start_mark) - self.descend_resolver(parent, index) - if self.check_event(ScalarEvent): - node = self.compose_scalar_node(anchor) - elif self.check_event(SequenceStartEvent): - node = self.compose_sequence_node(anchor) - elif self.check_event(MappingStartEvent): - node = self.compose_mapping_node(anchor) - self.ascend_resolver() - return node - - def compose_scalar_node(self, anchor): - event = self.get_event() - tag = event.tag - if tag is None or tag == '!': - tag = self.resolve(ScalarNode, event.value, event.implicit) - node = ScalarNode(tag, event.value, - event.start_mark, event.end_mark, style=event.style) - if anchor is not None: - self.anchors[anchor] = node - return node - - def compose_sequence_node(self, anchor): - start_event = self.get_event() - tag = start_event.tag - if tag is None or tag == '!': - tag = self.resolve(SequenceNode, None, start_event.implicit) - node = SequenceNode(tag, [], - start_event.start_mark, None, - flow_style=start_event.flow_style) - if anchor is not None: - self.anchors[anchor] = node - index = 0 - while not self.check_event(SequenceEndEvent): - node.value.append(self.compose_node(node, index)) - index += 1 - end_event = self.get_event() - node.end_mark = end_event.end_mark - return node - - def compose_mapping_node(self, anchor): - start_event = self.get_event() - tag = start_event.tag - if tag is None or tag == '!': - tag = self.resolve(MappingNode, None, start_event.implicit) - node = MappingNode(tag, [], - start_event.start_mark, None, - flow_style=start_event.flow_style) - if anchor is not None: - self.anchors[anchor] = node - while not self.check_event(MappingEndEvent): - #key_event = self.peek_event() - item_key = self.compose_node(node, None) - #if item_key in node.value: - # raise ComposerError("while composing a mapping", start_event.start_mark, - # "found duplicate key", key_event.start_mark) - item_value = self.compose_node(node, item_key) - #node.value[item_key] = item_value - node.value.append((item_key, item_value)) - end_event = self.get_event() - node.end_mark = end_event.end_mark - return node - diff --git a/coredns/venv/yaml/constructor.py b/coredns/venv/yaml/constructor.py deleted file mode 100644 index 1948b12..0000000 --- a/coredns/venv/yaml/constructor.py +++ /dev/null @@ -1,748 +0,0 @@ - -__all__ = [ - 'BaseConstructor', - 'SafeConstructor', - 'FullConstructor', - 'UnsafeConstructor', - 'Constructor', - 'ConstructorError' -] - -from .error import * -from .nodes import * - -import collections.abc, datetime, base64, binascii, re, sys, types - -class ConstructorError(MarkedYAMLError): - pass - -class BaseConstructor: - - yaml_constructors = {} - yaml_multi_constructors = {} - - def __init__(self): - self.constructed_objects = {} - self.recursive_objects = {} - self.state_generators = [] - self.deep_construct = False - - def check_data(self): - # If there are more documents available? - return self.check_node() - - def check_state_key(self, key): - """Block special attributes/methods from being set in a newly created - object, to prevent user-controlled methods from being called during - deserialization""" - if self.get_state_keys_blacklist_regexp().match(key): - raise ConstructorError(None, None, - "blacklisted key '%s' in instance state found" % (key,), None) - - def get_data(self): - # Construct and return the next document. - if self.check_node(): - return self.construct_document(self.get_node()) - - def get_single_data(self): - # Ensure that the stream contains a single document and construct it. - node = self.get_single_node() - if node is not None: - return self.construct_document(node) - return None - - def construct_document(self, node): - data = self.construct_object(node) - while self.state_generators: - state_generators = self.state_generators - self.state_generators = [] - for generator in state_generators: - for dummy in generator: - pass - self.constructed_objects = {} - self.recursive_objects = {} - self.deep_construct = False - return data - - def construct_object(self, node, deep=False): - if node in self.constructed_objects: - return self.constructed_objects[node] - if deep: - old_deep = self.deep_construct - self.deep_construct = True - if node in self.recursive_objects: - raise ConstructorError(None, None, - "found unconstructable recursive node", node.start_mark) - self.recursive_objects[node] = None - constructor = None - tag_suffix = None - if node.tag in self.yaml_constructors: - constructor = self.yaml_constructors[node.tag] - else: - for tag_prefix in self.yaml_multi_constructors: - if tag_prefix is not None and node.tag.startswith(tag_prefix): - tag_suffix = node.tag[len(tag_prefix):] - constructor = self.yaml_multi_constructors[tag_prefix] - break - else: - if None in self.yaml_multi_constructors: - tag_suffix = node.tag - constructor = self.yaml_multi_constructors[None] - elif None in self.yaml_constructors: - constructor = self.yaml_constructors[None] - elif isinstance(node, ScalarNode): - constructor = self.__class__.construct_scalar - elif isinstance(node, SequenceNode): - constructor = self.__class__.construct_sequence - elif isinstance(node, MappingNode): - constructor = self.__class__.construct_mapping - if tag_suffix is None: - data = constructor(self, node) - else: - data = constructor(self, tag_suffix, node) - if isinstance(data, types.GeneratorType): - generator = data - data = next(generator) - if self.deep_construct: - for dummy in generator: - pass - else: - self.state_generators.append(generator) - self.constructed_objects[node] = data - del self.recursive_objects[node] - if deep: - self.deep_construct = old_deep - return data - - def construct_scalar(self, node): - if not isinstance(node, ScalarNode): - raise ConstructorError(None, None, - "expected a scalar node, but found %s" % node.id, - node.start_mark) - return node.value - - def construct_sequence(self, node, deep=False): - if not isinstance(node, SequenceNode): - raise ConstructorError(None, None, - "expected a sequence node, but found %s" % node.id, - node.start_mark) - return [self.construct_object(child, deep=deep) - for child in node.value] - - def construct_mapping(self, node, deep=False): - if not isinstance(node, MappingNode): - raise ConstructorError(None, None, - "expected a mapping node, but found %s" % node.id, - node.start_mark) - mapping = {} - for key_node, value_node in node.value: - key = self.construct_object(key_node, deep=deep) - if not isinstance(key, collections.abc.Hashable): - raise ConstructorError("while constructing a mapping", node.start_mark, - "found unhashable key", key_node.start_mark) - value = self.construct_object(value_node, deep=deep) - mapping[key] = value - return mapping - - def construct_pairs(self, node, deep=False): - if not isinstance(node, MappingNode): - raise ConstructorError(None, None, - "expected a mapping node, but found %s" % node.id, - node.start_mark) - pairs = [] - for key_node, value_node in node.value: - key = self.construct_object(key_node, deep=deep) - value = self.construct_object(value_node, deep=deep) - pairs.append((key, value)) - return pairs - - @classmethod - def add_constructor(cls, tag, constructor): - if not 'yaml_constructors' in cls.__dict__: - cls.yaml_constructors = cls.yaml_constructors.copy() - cls.yaml_constructors[tag] = constructor - - @classmethod - def add_multi_constructor(cls, tag_prefix, multi_constructor): - if not 'yaml_multi_constructors' in cls.__dict__: - cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy() - cls.yaml_multi_constructors[tag_prefix] = multi_constructor - -class SafeConstructor(BaseConstructor): - - def construct_scalar(self, node): - if isinstance(node, MappingNode): - for key_node, value_node in node.value: - if key_node.tag == 'tag:yaml.org,2002:value': - return self.construct_scalar(value_node) - return super().construct_scalar(node) - - def flatten_mapping(self, node): - merge = [] - index = 0 - while index < len(node.value): - key_node, value_node = node.value[index] - if key_node.tag == 'tag:yaml.org,2002:merge': - del node.value[index] - if isinstance(value_node, MappingNode): - self.flatten_mapping(value_node) - merge.extend(value_node.value) - elif isinstance(value_node, SequenceNode): - submerge = [] - for subnode in value_node.value: - if not isinstance(subnode, MappingNode): - raise ConstructorError("while constructing a mapping", - node.start_mark, - "expected a mapping for merging, but found %s" - % subnode.id, subnode.start_mark) - self.flatten_mapping(subnode) - submerge.append(subnode.value) - submerge.reverse() - for value in submerge: - merge.extend(value) - else: - raise ConstructorError("while constructing a mapping", node.start_mark, - "expected a mapping or list of mappings for merging, but found %s" - % value_node.id, value_node.start_mark) - elif key_node.tag == 'tag:yaml.org,2002:value': - key_node.tag = 'tag:yaml.org,2002:str' - index += 1 - else: - index += 1 - if merge: - node.value = merge + node.value - - def construct_mapping(self, node, deep=False): - if isinstance(node, MappingNode): - self.flatten_mapping(node) - return super().construct_mapping(node, deep=deep) - - def construct_yaml_null(self, node): - self.construct_scalar(node) - return None - - bool_values = { - 'yes': True, - 'no': False, - 'true': True, - 'false': False, - 'on': True, - 'off': False, - } - - def construct_yaml_bool(self, node): - value = self.construct_scalar(node) - return self.bool_values[value.lower()] - - def construct_yaml_int(self, node): - value = self.construct_scalar(node) - value = value.replace('_', '') - sign = +1 - if value[0] == '-': - sign = -1 - if value[0] in '+-': - value = value[1:] - if value == '0': - return 0 - elif value.startswith('0b'): - return sign*int(value[2:], 2) - elif value.startswith('0x'): - return sign*int(value[2:], 16) - elif value[0] == '0': - return sign*int(value, 8) - elif ':' in value: - digits = [int(part) for part in value.split(':')] - digits.reverse() - base = 1 - value = 0 - for digit in digits: - value += digit*base - base *= 60 - return sign*value - else: - return sign*int(value) - - inf_value = 1e300 - while inf_value != inf_value*inf_value: - inf_value *= inf_value - nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99). - - def construct_yaml_float(self, node): - value = self.construct_scalar(node) - value = value.replace('_', '').lower() - sign = +1 - if value[0] == '-': - sign = -1 - if value[0] in '+-': - value = value[1:] - if value == '.inf': - return sign*self.inf_value - elif value == '.nan': - return self.nan_value - elif ':' in value: - digits = [float(part) for part in value.split(':')] - digits.reverse() - base = 1 - value = 0.0 - for digit in digits: - value += digit*base - base *= 60 - return sign*value - else: - return sign*float(value) - - def construct_yaml_binary(self, node): - try: - value = self.construct_scalar(node).encode('ascii') - except UnicodeEncodeError as exc: - raise ConstructorError(None, None, - "failed to convert base64 data into ascii: %s" % exc, - node.start_mark) - try: - if hasattr(base64, 'decodebytes'): - return base64.decodebytes(value) - else: - return base64.decodestring(value) - except binascii.Error as exc: - raise ConstructorError(None, None, - "failed to decode base64 data: %s" % exc, node.start_mark) - - timestamp_regexp = re.compile( - r'''^(?P[0-9][0-9][0-9][0-9]) - -(?P[0-9][0-9]?) - -(?P[0-9][0-9]?) - (?:(?:[Tt]|[ \t]+) - (?P[0-9][0-9]?) - :(?P[0-9][0-9]) - :(?P[0-9][0-9]) - (?:\.(?P[0-9]*))? - (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) - (?::(?P[0-9][0-9]))?))?)?$''', re.X) - - def construct_yaml_timestamp(self, node): - value = self.construct_scalar(node) - match = self.timestamp_regexp.match(node.value) - values = match.groupdict() - year = int(values['year']) - month = int(values['month']) - day = int(values['day']) - if not values['hour']: - return datetime.date(year, month, day) - hour = int(values['hour']) - minute = int(values['minute']) - second = int(values['second']) - fraction = 0 - tzinfo = None - if values['fraction']: - fraction = values['fraction'][:6] - while len(fraction) < 6: - fraction += '0' - fraction = int(fraction) - if values['tz_sign']: - tz_hour = int(values['tz_hour']) - tz_minute = int(values['tz_minute'] or 0) - delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) - if values['tz_sign'] == '-': - delta = -delta - tzinfo = datetime.timezone(delta) - elif values['tz']: - tzinfo = datetime.timezone.utc - return datetime.datetime(year, month, day, hour, minute, second, fraction, - tzinfo=tzinfo) - - def construct_yaml_omap(self, node): - # Note: we do not check for duplicate keys, because it's too - # CPU-expensive. - omap = [] - yield omap - if not isinstance(node, SequenceNode): - raise ConstructorError("while constructing an ordered map", node.start_mark, - "expected a sequence, but found %s" % node.id, node.start_mark) - for subnode in node.value: - if not isinstance(subnode, MappingNode): - raise ConstructorError("while constructing an ordered map", node.start_mark, - "expected a mapping of length 1, but found %s" % subnode.id, - subnode.start_mark) - if len(subnode.value) != 1: - raise ConstructorError("while constructing an ordered map", node.start_mark, - "expected a single mapping item, but found %d items" % len(subnode.value), - subnode.start_mark) - key_node, value_node = subnode.value[0] - key = self.construct_object(key_node) - value = self.construct_object(value_node) - omap.append((key, value)) - - def construct_yaml_pairs(self, node): - # Note: the same code as `construct_yaml_omap`. - pairs = [] - yield pairs - if not isinstance(node, SequenceNode): - raise ConstructorError("while constructing pairs", node.start_mark, - "expected a sequence, but found %s" % node.id, node.start_mark) - for subnode in node.value: - if not isinstance(subnode, MappingNode): - raise ConstructorError("while constructing pairs", node.start_mark, - "expected a mapping of length 1, but found %s" % subnode.id, - subnode.start_mark) - if len(subnode.value) != 1: - raise ConstructorError("while constructing pairs", node.start_mark, - "expected a single mapping item, but found %d items" % len(subnode.value), - subnode.start_mark) - key_node, value_node = subnode.value[0] - key = self.construct_object(key_node) - value = self.construct_object(value_node) - pairs.append((key, value)) - - def construct_yaml_set(self, node): - data = set() - yield data - value = self.construct_mapping(node) - data.update(value) - - def construct_yaml_str(self, node): - return self.construct_scalar(node) - - def construct_yaml_seq(self, node): - data = [] - yield data - data.extend(self.construct_sequence(node)) - - def construct_yaml_map(self, node): - data = {} - yield data - value = self.construct_mapping(node) - data.update(value) - - def construct_yaml_object(self, node, cls): - data = cls.__new__(cls) - yield data - if hasattr(data, '__setstate__'): - state = self.construct_mapping(node, deep=True) - data.__setstate__(state) - else: - state = self.construct_mapping(node) - data.__dict__.update(state) - - def construct_undefined(self, node): - raise ConstructorError(None, None, - "could not determine a constructor for the tag %r" % node.tag, - node.start_mark) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:null', - SafeConstructor.construct_yaml_null) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:bool', - SafeConstructor.construct_yaml_bool) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:int', - SafeConstructor.construct_yaml_int) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:float', - SafeConstructor.construct_yaml_float) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:binary', - SafeConstructor.construct_yaml_binary) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:timestamp', - SafeConstructor.construct_yaml_timestamp) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:omap', - SafeConstructor.construct_yaml_omap) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:pairs', - SafeConstructor.construct_yaml_pairs) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:set', - SafeConstructor.construct_yaml_set) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:str', - SafeConstructor.construct_yaml_str) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:seq', - SafeConstructor.construct_yaml_seq) - -SafeConstructor.add_constructor( - 'tag:yaml.org,2002:map', - SafeConstructor.construct_yaml_map) - -SafeConstructor.add_constructor(None, - SafeConstructor.construct_undefined) - -class FullConstructor(SafeConstructor): - # 'extend' is blacklisted because it is used by - # construct_python_object_apply to add `listitems` to a newly generate - # python instance - def get_state_keys_blacklist(self): - return ['^extend$', '^__.*__$'] - - def get_state_keys_blacklist_regexp(self): - if not hasattr(self, 'state_keys_blacklist_regexp'): - self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')') - return self.state_keys_blacklist_regexp - - def construct_python_str(self, node): - return self.construct_scalar(node) - - def construct_python_unicode(self, node): - return self.construct_scalar(node) - - def construct_python_bytes(self, node): - try: - value = self.construct_scalar(node).encode('ascii') - except UnicodeEncodeError as exc: - raise ConstructorError(None, None, - "failed to convert base64 data into ascii: %s" % exc, - node.start_mark) - try: - if hasattr(base64, 'decodebytes'): - return base64.decodebytes(value) - else: - return base64.decodestring(value) - except binascii.Error as exc: - raise ConstructorError(None, None, - "failed to decode base64 data: %s" % exc, node.start_mark) - - def construct_python_long(self, node): - return self.construct_yaml_int(node) - - def construct_python_complex(self, node): - return complex(self.construct_scalar(node)) - - def construct_python_tuple(self, node): - return tuple(self.construct_sequence(node)) - - def find_python_module(self, name, mark, unsafe=False): - if not name: - raise ConstructorError("while constructing a Python module", mark, - "expected non-empty name appended to the tag", mark) - if unsafe: - try: - __import__(name) - except ImportError as exc: - raise ConstructorError("while constructing a Python module", mark, - "cannot find module %r (%s)" % (name, exc), mark) - if name not in sys.modules: - raise ConstructorError("while constructing a Python module", mark, - "module %r is not imported" % name, mark) - return sys.modules[name] - - def find_python_name(self, name, mark, unsafe=False): - if not name: - raise ConstructorError("while constructing a Python object", mark, - "expected non-empty name appended to the tag", mark) - if '.' in name: - module_name, object_name = name.rsplit('.', 1) - else: - module_name = 'builtins' - object_name = name - if unsafe: - try: - __import__(module_name) - except ImportError as exc: - raise ConstructorError("while constructing a Python object", mark, - "cannot find module %r (%s)" % (module_name, exc), mark) - if module_name not in sys.modules: - raise ConstructorError("while constructing a Python object", mark, - "module %r is not imported" % module_name, mark) - module = sys.modules[module_name] - if not hasattr(module, object_name): - raise ConstructorError("while constructing a Python object", mark, - "cannot find %r in the module %r" - % (object_name, module.__name__), mark) - return getattr(module, object_name) - - def construct_python_name(self, suffix, node): - value = self.construct_scalar(node) - if value: - raise ConstructorError("while constructing a Python name", node.start_mark, - "expected the empty value, but found %r" % value, node.start_mark) - return self.find_python_name(suffix, node.start_mark) - - def construct_python_module(self, suffix, node): - value = self.construct_scalar(node) - if value: - raise ConstructorError("while constructing a Python module", node.start_mark, - "expected the empty value, but found %r" % value, node.start_mark) - return self.find_python_module(suffix, node.start_mark) - - def make_python_instance(self, suffix, node, - args=None, kwds=None, newobj=False, unsafe=False): - if not args: - args = [] - if not kwds: - kwds = {} - cls = self.find_python_name(suffix, node.start_mark) - if not (unsafe or isinstance(cls, type)): - raise ConstructorError("while constructing a Python instance", node.start_mark, - "expected a class, but found %r" % type(cls), - node.start_mark) - if newobj and isinstance(cls, type): - return cls.__new__(cls, *args, **kwds) - else: - return cls(*args, **kwds) - - def set_python_instance_state(self, instance, state, unsafe=False): - if hasattr(instance, '__setstate__'): - instance.__setstate__(state) - else: - slotstate = {} - if isinstance(state, tuple) and len(state) == 2: - state, slotstate = state - if hasattr(instance, '__dict__'): - if not unsafe and state: - for key in state.keys(): - self.check_state_key(key) - instance.__dict__.update(state) - elif state: - slotstate.update(state) - for key, value in slotstate.items(): - if not unsafe: - self.check_state_key(key) - setattr(instance, key, value) - - def construct_python_object(self, suffix, node): - # Format: - # !!python/object:module.name { ... state ... } - instance = self.make_python_instance(suffix, node, newobj=True) - yield instance - deep = hasattr(instance, '__setstate__') - state = self.construct_mapping(node, deep=deep) - self.set_python_instance_state(instance, state) - - def construct_python_object_apply(self, suffix, node, newobj=False): - # Format: - # !!python/object/apply # (or !!python/object/new) - # args: [ ... arguments ... ] - # kwds: { ... keywords ... } - # state: ... state ... - # listitems: [ ... listitems ... ] - # dictitems: { ... dictitems ... } - # or short format: - # !!python/object/apply [ ... arguments ... ] - # The difference between !!python/object/apply and !!python/object/new - # is how an object is created, check make_python_instance for details. - if isinstance(node, SequenceNode): - args = self.construct_sequence(node, deep=True) - kwds = {} - state = {} - listitems = [] - dictitems = {} - else: - value = self.construct_mapping(node, deep=True) - args = value.get('args', []) - kwds = value.get('kwds', {}) - state = value.get('state', {}) - listitems = value.get('listitems', []) - dictitems = value.get('dictitems', {}) - instance = self.make_python_instance(suffix, node, args, kwds, newobj) - if state: - self.set_python_instance_state(instance, state) - if listitems: - instance.extend(listitems) - if dictitems: - for key in dictitems: - instance[key] = dictitems[key] - return instance - - def construct_python_object_new(self, suffix, node): - return self.construct_python_object_apply(suffix, node, newobj=True) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/none', - FullConstructor.construct_yaml_null) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/bool', - FullConstructor.construct_yaml_bool) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/str', - FullConstructor.construct_python_str) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/unicode', - FullConstructor.construct_python_unicode) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/bytes', - FullConstructor.construct_python_bytes) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/int', - FullConstructor.construct_yaml_int) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/long', - FullConstructor.construct_python_long) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/float', - FullConstructor.construct_yaml_float) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/complex', - FullConstructor.construct_python_complex) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/list', - FullConstructor.construct_yaml_seq) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/tuple', - FullConstructor.construct_python_tuple) - -FullConstructor.add_constructor( - 'tag:yaml.org,2002:python/dict', - FullConstructor.construct_yaml_map) - -FullConstructor.add_multi_constructor( - 'tag:yaml.org,2002:python/name:', - FullConstructor.construct_python_name) - -FullConstructor.add_multi_constructor( - 'tag:yaml.org,2002:python/module:', - FullConstructor.construct_python_module) - -FullConstructor.add_multi_constructor( - 'tag:yaml.org,2002:python/object:', - FullConstructor.construct_python_object) - -FullConstructor.add_multi_constructor( - 'tag:yaml.org,2002:python/object/new:', - FullConstructor.construct_python_object_new) - -class UnsafeConstructor(FullConstructor): - - def find_python_module(self, name, mark): - return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True) - - def find_python_name(self, name, mark): - return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True) - - def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False): - return super(UnsafeConstructor, self).make_python_instance( - suffix, node, args, kwds, newobj, unsafe=True) - - def set_python_instance_state(self, instance, state): - return super(UnsafeConstructor, self).set_python_instance_state( - instance, state, unsafe=True) - -UnsafeConstructor.add_multi_constructor( - 'tag:yaml.org,2002:python/object/apply:', - UnsafeConstructor.construct_python_object_apply) - -# Constructor is same as UnsafeConstructor. Need to leave this in place in case -# people have extended it directly. -class Constructor(UnsafeConstructor): - pass diff --git a/coredns/venv/yaml/cyaml.py b/coredns/venv/yaml/cyaml.py deleted file mode 100644 index 1e606c7..0000000 --- a/coredns/venv/yaml/cyaml.py +++ /dev/null @@ -1,101 +0,0 @@ - -__all__ = [ - 'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader', - 'CBaseDumper', 'CSafeDumper', 'CDumper' -] - -from _yaml import CParser, CEmitter - -from .constructor import * - -from .serializer import * -from .representer import * - -from .resolver import * - -class CBaseLoader(CParser, BaseConstructor, BaseResolver): - - def __init__(self, stream): - CParser.__init__(self, stream) - BaseConstructor.__init__(self) - BaseResolver.__init__(self) - -class CSafeLoader(CParser, SafeConstructor, Resolver): - - def __init__(self, stream): - CParser.__init__(self, stream) - SafeConstructor.__init__(self) - Resolver.__init__(self) - -class CFullLoader(CParser, FullConstructor, Resolver): - - def __init__(self, stream): - CParser.__init__(self, stream) - FullConstructor.__init__(self) - Resolver.__init__(self) - -class CUnsafeLoader(CParser, UnsafeConstructor, Resolver): - - def __init__(self, stream): - CParser.__init__(self, stream) - UnsafeConstructor.__init__(self) - Resolver.__init__(self) - -class CLoader(CParser, Constructor, Resolver): - - def __init__(self, stream): - CParser.__init__(self, stream) - Constructor.__init__(self) - Resolver.__init__(self) - -class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver): - - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - CEmitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, encoding=encoding, - allow_unicode=allow_unicode, line_break=line_break, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - Representer.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) - Resolver.__init__(self) - -class CSafeDumper(CEmitter, SafeRepresenter, Resolver): - - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - CEmitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, encoding=encoding, - allow_unicode=allow_unicode, line_break=line_break, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - SafeRepresenter.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) - Resolver.__init__(self) - -class CDumper(CEmitter, Serializer, Representer, Resolver): - - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - CEmitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, encoding=encoding, - allow_unicode=allow_unicode, line_break=line_break, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - Representer.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) - Resolver.__init__(self) - diff --git a/coredns/venv/yaml/dumper.py b/coredns/venv/yaml/dumper.py deleted file mode 100644 index 6aadba5..0000000 --- a/coredns/venv/yaml/dumper.py +++ /dev/null @@ -1,62 +0,0 @@ - -__all__ = ['BaseDumper', 'SafeDumper', 'Dumper'] - -from .emitter import * -from .serializer import * -from .representer import * -from .resolver import * - -class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver): - - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - Emitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break) - Serializer.__init__(self, encoding=encoding, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - Representer.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) - Resolver.__init__(self) - -class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver): - - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - Emitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break) - Serializer.__init__(self, encoding=encoding, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - SafeRepresenter.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) - Resolver.__init__(self) - -class Dumper(Emitter, Serializer, Representer, Resolver): - - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - Emitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break) - Serializer.__init__(self, encoding=encoding, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - Representer.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) - Resolver.__init__(self) - diff --git a/coredns/venv/yaml/emitter.py b/coredns/venv/yaml/emitter.py deleted file mode 100644 index a664d01..0000000 --- a/coredns/venv/yaml/emitter.py +++ /dev/null @@ -1,1137 +0,0 @@ - -# Emitter expects events obeying the following grammar: -# stream ::= STREAM-START document* STREAM-END -# document ::= DOCUMENT-START node DOCUMENT-END -# node ::= SCALAR | sequence | mapping -# sequence ::= SEQUENCE-START node* SEQUENCE-END -# mapping ::= MAPPING-START (node node)* MAPPING-END - -__all__ = ['Emitter', 'EmitterError'] - -from .error import YAMLError -from .events import * - -class EmitterError(YAMLError): - pass - -class ScalarAnalysis: - def __init__(self, scalar, empty, multiline, - allow_flow_plain, allow_block_plain, - allow_single_quoted, allow_double_quoted, - allow_block): - self.scalar = scalar - self.empty = empty - self.multiline = multiline - self.allow_flow_plain = allow_flow_plain - self.allow_block_plain = allow_block_plain - self.allow_single_quoted = allow_single_quoted - self.allow_double_quoted = allow_double_quoted - self.allow_block = allow_block - -class Emitter: - - DEFAULT_TAG_PREFIXES = { - '!' : '!', - 'tag:yaml.org,2002:' : '!!', - } - - def __init__(self, stream, canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None): - - # The stream should have the methods `write` and possibly `flush`. - self.stream = stream - - # Encoding can be overridden by STREAM-START. - self.encoding = None - - # Emitter is a state machine with a stack of states to handle nested - # structures. - self.states = [] - self.state = self.expect_stream_start - - # Current event and the event queue. - self.events = [] - self.event = None - - # The current indentation level and the stack of previous indents. - self.indents = [] - self.indent = None - - # Flow level. - self.flow_level = 0 - - # Contexts. - self.root_context = False - self.sequence_context = False - self.mapping_context = False - self.simple_key_context = False - - # Characteristics of the last emitted character: - # - current position. - # - is it a whitespace? - # - is it an indention character - # (indentation space, '-', '?', or ':')? - self.line = 0 - self.column = 0 - self.whitespace = True - self.indention = True - - # Whether the document requires an explicit document indicator - self.open_ended = False - - # Formatting details. - self.canonical = canonical - self.allow_unicode = allow_unicode - self.best_indent = 2 - if indent and 1 < indent < 10: - self.best_indent = indent - self.best_width = 80 - if width and width > self.best_indent*2: - self.best_width = width - self.best_line_break = '\n' - if line_break in ['\r', '\n', '\r\n']: - self.best_line_break = line_break - - # Tag prefixes. - self.tag_prefixes = None - - # Prepared anchor and tag. - self.prepared_anchor = None - self.prepared_tag = None - - # Scalar analysis and style. - self.analysis = None - self.style = None - - def dispose(self): - # Reset the state attributes (to clear self-references) - self.states = [] - self.state = None - - def emit(self, event): - self.events.append(event) - while not self.need_more_events(): - self.event = self.events.pop(0) - self.state() - self.event = None - - # In some cases, we wait for a few next events before emitting. - - def need_more_events(self): - if not self.events: - return True - event = self.events[0] - if isinstance(event, DocumentStartEvent): - return self.need_events(1) - elif isinstance(event, SequenceStartEvent): - return self.need_events(2) - elif isinstance(event, MappingStartEvent): - return self.need_events(3) - else: - return False - - def need_events(self, count): - level = 0 - for event in self.events[1:]: - if isinstance(event, (DocumentStartEvent, CollectionStartEvent)): - level += 1 - elif isinstance(event, (DocumentEndEvent, CollectionEndEvent)): - level -= 1 - elif isinstance(event, StreamEndEvent): - level = -1 - if level < 0: - return False - return (len(self.events) < count+1) - - def increase_indent(self, flow=False, indentless=False): - self.indents.append(self.indent) - if self.indent is None: - if flow: - self.indent = self.best_indent - else: - self.indent = 0 - elif not indentless: - self.indent += self.best_indent - - # States. - - # Stream handlers. - - def expect_stream_start(self): - if isinstance(self.event, StreamStartEvent): - if self.event.encoding and not hasattr(self.stream, 'encoding'): - self.encoding = self.event.encoding - self.write_stream_start() - self.state = self.expect_first_document_start - else: - raise EmitterError("expected StreamStartEvent, but got %s" - % self.event) - - def expect_nothing(self): - raise EmitterError("expected nothing, but got %s" % self.event) - - # Document handlers. - - def expect_first_document_start(self): - return self.expect_document_start(first=True) - - def expect_document_start(self, first=False): - if isinstance(self.event, DocumentStartEvent): - if (self.event.version or self.event.tags) and self.open_ended: - self.write_indicator('...', True) - self.write_indent() - if self.event.version: - version_text = self.prepare_version(self.event.version) - self.write_version_directive(version_text) - self.tag_prefixes = self.DEFAULT_TAG_PREFIXES.copy() - if self.event.tags: - handles = sorted(self.event.tags.keys()) - for handle in handles: - prefix = self.event.tags[handle] - self.tag_prefixes[prefix] = handle - handle_text = self.prepare_tag_handle(handle) - prefix_text = self.prepare_tag_prefix(prefix) - self.write_tag_directive(handle_text, prefix_text) - implicit = (first and not self.event.explicit and not self.canonical - and not self.event.version and not self.event.tags - and not self.check_empty_document()) - if not implicit: - self.write_indent() - self.write_indicator('---', True) - if self.canonical: - self.write_indent() - self.state = self.expect_document_root - elif isinstance(self.event, StreamEndEvent): - if self.open_ended: - self.write_indicator('...', True) - self.write_indent() - self.write_stream_end() - self.state = self.expect_nothing - else: - raise EmitterError("expected DocumentStartEvent, but got %s" - % self.event) - - def expect_document_end(self): - if isinstance(self.event, DocumentEndEvent): - self.write_indent() - if self.event.explicit: - self.write_indicator('...', True) - self.write_indent() - self.flush_stream() - self.state = self.expect_document_start - else: - raise EmitterError("expected DocumentEndEvent, but got %s" - % self.event) - - def expect_document_root(self): - self.states.append(self.expect_document_end) - self.expect_node(root=True) - - # Node handlers. - - def expect_node(self, root=False, sequence=False, mapping=False, - simple_key=False): - self.root_context = root - self.sequence_context = sequence - self.mapping_context = mapping - self.simple_key_context = simple_key - if isinstance(self.event, AliasEvent): - self.expect_alias() - elif isinstance(self.event, (ScalarEvent, CollectionStartEvent)): - self.process_anchor('&') - self.process_tag() - if isinstance(self.event, ScalarEvent): - self.expect_scalar() - elif isinstance(self.event, SequenceStartEvent): - if self.flow_level or self.canonical or self.event.flow_style \ - or self.check_empty_sequence(): - self.expect_flow_sequence() - else: - self.expect_block_sequence() - elif isinstance(self.event, MappingStartEvent): - if self.flow_level or self.canonical or self.event.flow_style \ - or self.check_empty_mapping(): - self.expect_flow_mapping() - else: - self.expect_block_mapping() - else: - raise EmitterError("expected NodeEvent, but got %s" % self.event) - - def expect_alias(self): - if self.event.anchor is None: - raise EmitterError("anchor is not specified for alias") - self.process_anchor('*') - self.state = self.states.pop() - - def expect_scalar(self): - self.increase_indent(flow=True) - self.process_scalar() - self.indent = self.indents.pop() - self.state = self.states.pop() - - # Flow sequence handlers. - - def expect_flow_sequence(self): - self.write_indicator('[', True, whitespace=True) - self.flow_level += 1 - self.increase_indent(flow=True) - self.state = self.expect_first_flow_sequence_item - - def expect_first_flow_sequence_item(self): - if isinstance(self.event, SequenceEndEvent): - self.indent = self.indents.pop() - self.flow_level -= 1 - self.write_indicator(']', False) - self.state = self.states.pop() - else: - if self.canonical or self.column > self.best_width: - self.write_indent() - self.states.append(self.expect_flow_sequence_item) - self.expect_node(sequence=True) - - def expect_flow_sequence_item(self): - if isinstance(self.event, SequenceEndEvent): - self.indent = self.indents.pop() - self.flow_level -= 1 - if self.canonical: - self.write_indicator(',', False) - self.write_indent() - self.write_indicator(']', False) - self.state = self.states.pop() - else: - self.write_indicator(',', False) - if self.canonical or self.column > self.best_width: - self.write_indent() - self.states.append(self.expect_flow_sequence_item) - self.expect_node(sequence=True) - - # Flow mapping handlers. - - def expect_flow_mapping(self): - self.write_indicator('{', True, whitespace=True) - self.flow_level += 1 - self.increase_indent(flow=True) - self.state = self.expect_first_flow_mapping_key - - def expect_first_flow_mapping_key(self): - if isinstance(self.event, MappingEndEvent): - self.indent = self.indents.pop() - self.flow_level -= 1 - self.write_indicator('}', False) - self.state = self.states.pop() - else: - if self.canonical or self.column > self.best_width: - self.write_indent() - if not self.canonical and self.check_simple_key(): - self.states.append(self.expect_flow_mapping_simple_value) - self.expect_node(mapping=True, simple_key=True) - else: - self.write_indicator('?', True) - self.states.append(self.expect_flow_mapping_value) - self.expect_node(mapping=True) - - def expect_flow_mapping_key(self): - if isinstance(self.event, MappingEndEvent): - self.indent = self.indents.pop() - self.flow_level -= 1 - if self.canonical: - self.write_indicator(',', False) - self.write_indent() - self.write_indicator('}', False) - self.state = self.states.pop() - else: - self.write_indicator(',', False) - if self.canonical or self.column > self.best_width: - self.write_indent() - if not self.canonical and self.check_simple_key(): - self.states.append(self.expect_flow_mapping_simple_value) - self.expect_node(mapping=True, simple_key=True) - else: - self.write_indicator('?', True) - self.states.append(self.expect_flow_mapping_value) - self.expect_node(mapping=True) - - def expect_flow_mapping_simple_value(self): - self.write_indicator(':', False) - self.states.append(self.expect_flow_mapping_key) - self.expect_node(mapping=True) - - def expect_flow_mapping_value(self): - if self.canonical or self.column > self.best_width: - self.write_indent() - self.write_indicator(':', True) - self.states.append(self.expect_flow_mapping_key) - self.expect_node(mapping=True) - - # Block sequence handlers. - - def expect_block_sequence(self): - indentless = (self.mapping_context and not self.indention) - self.increase_indent(flow=False, indentless=indentless) - self.state = self.expect_first_block_sequence_item - - def expect_first_block_sequence_item(self): - return self.expect_block_sequence_item(first=True) - - def expect_block_sequence_item(self, first=False): - if not first and isinstance(self.event, SequenceEndEvent): - self.indent = self.indents.pop() - self.state = self.states.pop() - else: - self.write_indent() - self.write_indicator('-', True, indention=True) - self.states.append(self.expect_block_sequence_item) - self.expect_node(sequence=True) - - # Block mapping handlers. - - def expect_block_mapping(self): - self.increase_indent(flow=False) - self.state = self.expect_first_block_mapping_key - - def expect_first_block_mapping_key(self): - return self.expect_block_mapping_key(first=True) - - def expect_block_mapping_key(self, first=False): - if not first and isinstance(self.event, MappingEndEvent): - self.indent = self.indents.pop() - self.state = self.states.pop() - else: - self.write_indent() - if self.check_simple_key(): - self.states.append(self.expect_block_mapping_simple_value) - self.expect_node(mapping=True, simple_key=True) - else: - self.write_indicator('?', True, indention=True) - self.states.append(self.expect_block_mapping_value) - self.expect_node(mapping=True) - - def expect_block_mapping_simple_value(self): - self.write_indicator(':', False) - self.states.append(self.expect_block_mapping_key) - self.expect_node(mapping=True) - - def expect_block_mapping_value(self): - self.write_indent() - self.write_indicator(':', True, indention=True) - self.states.append(self.expect_block_mapping_key) - self.expect_node(mapping=True) - - # Checkers. - - def check_empty_sequence(self): - return (isinstance(self.event, SequenceStartEvent) and self.events - and isinstance(self.events[0], SequenceEndEvent)) - - def check_empty_mapping(self): - return (isinstance(self.event, MappingStartEvent) and self.events - and isinstance(self.events[0], MappingEndEvent)) - - def check_empty_document(self): - if not isinstance(self.event, DocumentStartEvent) or not self.events: - return False - event = self.events[0] - return (isinstance(event, ScalarEvent) and event.anchor is None - and event.tag is None and event.implicit and event.value == '') - - def check_simple_key(self): - length = 0 - if isinstance(self.event, NodeEvent) and self.event.anchor is not None: - if self.prepared_anchor is None: - self.prepared_anchor = self.prepare_anchor(self.event.anchor) - length += len(self.prepared_anchor) - if isinstance(self.event, (ScalarEvent, CollectionStartEvent)) \ - and self.event.tag is not None: - if self.prepared_tag is None: - self.prepared_tag = self.prepare_tag(self.event.tag) - length += len(self.prepared_tag) - if isinstance(self.event, ScalarEvent): - if self.analysis is None: - self.analysis = self.analyze_scalar(self.event.value) - length += len(self.analysis.scalar) - return (length < 128 and (isinstance(self.event, AliasEvent) - or (isinstance(self.event, ScalarEvent) - and not self.analysis.empty and not self.analysis.multiline) - or self.check_empty_sequence() or self.check_empty_mapping())) - - # Anchor, Tag, and Scalar processors. - - def process_anchor(self, indicator): - if self.event.anchor is None: - self.prepared_anchor = None - return - if self.prepared_anchor is None: - self.prepared_anchor = self.prepare_anchor(self.event.anchor) - if self.prepared_anchor: - self.write_indicator(indicator+self.prepared_anchor, True) - self.prepared_anchor = None - - def process_tag(self): - tag = self.event.tag - if isinstance(self.event, ScalarEvent): - if self.style is None: - self.style = self.choose_scalar_style() - if ((not self.canonical or tag is None) and - ((self.style == '' and self.event.implicit[0]) - or (self.style != '' and self.event.implicit[1]))): - self.prepared_tag = None - return - if self.event.implicit[0] and tag is None: - tag = '!' - self.prepared_tag = None - else: - if (not self.canonical or tag is None) and self.event.implicit: - self.prepared_tag = None - return - if tag is None: - raise EmitterError("tag is not specified") - if self.prepared_tag is None: - self.prepared_tag = self.prepare_tag(tag) - if self.prepared_tag: - self.write_indicator(self.prepared_tag, True) - self.prepared_tag = None - - def choose_scalar_style(self): - if self.analysis is None: - self.analysis = self.analyze_scalar(self.event.value) - if self.event.style == '"' or self.canonical: - return '"' - if not self.event.style and self.event.implicit[0]: - if (not (self.simple_key_context and - (self.analysis.empty or self.analysis.multiline)) - and (self.flow_level and self.analysis.allow_flow_plain - or (not self.flow_level and self.analysis.allow_block_plain))): - return '' - if self.event.style and self.event.style in '|>': - if (not self.flow_level and not self.simple_key_context - and self.analysis.allow_block): - return self.event.style - if not self.event.style or self.event.style == '\'': - if (self.analysis.allow_single_quoted and - not (self.simple_key_context and self.analysis.multiline)): - return '\'' - return '"' - - def process_scalar(self): - if self.analysis is None: - self.analysis = self.analyze_scalar(self.event.value) - if self.style is None: - self.style = self.choose_scalar_style() - split = (not self.simple_key_context) - #if self.analysis.multiline and split \ - # and (not self.style or self.style in '\'\"'): - # self.write_indent() - if self.style == '"': - self.write_double_quoted(self.analysis.scalar, split) - elif self.style == '\'': - self.write_single_quoted(self.analysis.scalar, split) - elif self.style == '>': - self.write_folded(self.analysis.scalar) - elif self.style == '|': - self.write_literal(self.analysis.scalar) - else: - self.write_plain(self.analysis.scalar, split) - self.analysis = None - self.style = None - - # Analyzers. - - def prepare_version(self, version): - major, minor = version - if major != 1: - raise EmitterError("unsupported YAML version: %d.%d" % (major, minor)) - return '%d.%d' % (major, minor) - - def prepare_tag_handle(self, handle): - if not handle: - raise EmitterError("tag handle must not be empty") - if handle[0] != '!' or handle[-1] != '!': - raise EmitterError("tag handle must start and end with '!': %r" % handle) - for ch in handle[1:-1]: - if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-_'): - raise EmitterError("invalid character %r in the tag handle: %r" - % (ch, handle)) - return handle - - def prepare_tag_prefix(self, prefix): - if not prefix: - raise EmitterError("tag prefix must not be empty") - chunks = [] - start = end = 0 - if prefix[0] == '!': - end = 1 - while end < len(prefix): - ch = prefix[end] - if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-;/?!:@&=+$,_.~*\'()[]': - end += 1 - else: - if start < end: - chunks.append(prefix[start:end]) - start = end = end+1 - data = ch.encode('utf-8') - for ch in data: - chunks.append('%%%02X' % ord(ch)) - if start < end: - chunks.append(prefix[start:end]) - return ''.join(chunks) - - def prepare_tag(self, tag): - if not tag: - raise EmitterError("tag must not be empty") - if tag == '!': - return tag - handle = None - suffix = tag - prefixes = sorted(self.tag_prefixes.keys()) - for prefix in prefixes: - if tag.startswith(prefix) \ - and (prefix == '!' or len(prefix) < len(tag)): - handle = self.tag_prefixes[prefix] - suffix = tag[len(prefix):] - chunks = [] - start = end = 0 - while end < len(suffix): - ch = suffix[end] - if '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-;/?:@&=+$,_.~*\'()[]' \ - or (ch == '!' and handle != '!'): - end += 1 - else: - if start < end: - chunks.append(suffix[start:end]) - start = end = end+1 - data = ch.encode('utf-8') - for ch in data: - chunks.append('%%%02X' % ch) - if start < end: - chunks.append(suffix[start:end]) - suffix_text = ''.join(chunks) - if handle: - return '%s%s' % (handle, suffix_text) - else: - return '!<%s>' % suffix_text - - def prepare_anchor(self, anchor): - if not anchor: - raise EmitterError("anchor must not be empty") - for ch in anchor: - if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-_'): - raise EmitterError("invalid character %r in the anchor: %r" - % (ch, anchor)) - return anchor - - def analyze_scalar(self, scalar): - - # Empty scalar is a special case. - if not scalar: - return ScalarAnalysis(scalar=scalar, empty=True, multiline=False, - allow_flow_plain=False, allow_block_plain=True, - allow_single_quoted=True, allow_double_quoted=True, - allow_block=False) - - # Indicators and special characters. - block_indicators = False - flow_indicators = False - line_breaks = False - special_characters = False - - # Important whitespace combinations. - leading_space = False - leading_break = False - trailing_space = False - trailing_break = False - break_space = False - space_break = False - - # Check document indicators. - if scalar.startswith('---') or scalar.startswith('...'): - block_indicators = True - flow_indicators = True - - # First character or preceded by a whitespace. - preceded_by_whitespace = True - - # Last character or followed by a whitespace. - followed_by_whitespace = (len(scalar) == 1 or - scalar[1] in '\0 \t\r\n\x85\u2028\u2029') - - # The previous character is a space. - previous_space = False - - # The previous character is a break. - previous_break = False - - index = 0 - while index < len(scalar): - ch = scalar[index] - - # Check for indicators. - if index == 0: - # Leading indicators are special characters. - if ch in '#,[]{}&*!|>\'\"%@`': - flow_indicators = True - block_indicators = True - if ch in '?:': - flow_indicators = True - if followed_by_whitespace: - block_indicators = True - if ch == '-' and followed_by_whitespace: - flow_indicators = True - block_indicators = True - else: - # Some indicators cannot appear within a scalar as well. - if ch in ',?[]{}': - flow_indicators = True - if ch == ':': - flow_indicators = True - if followed_by_whitespace: - block_indicators = True - if ch == '#' and preceded_by_whitespace: - flow_indicators = True - block_indicators = True - - # Check for line breaks, special, and unicode characters. - if ch in '\n\x85\u2028\u2029': - line_breaks = True - if not (ch == '\n' or '\x20' <= ch <= '\x7E'): - if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF' - or '\uE000' <= ch <= '\uFFFD' - or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF': - unicode_characters = True - if not self.allow_unicode: - special_characters = True - else: - special_characters = True - - # Detect important whitespace combinations. - if ch == ' ': - if index == 0: - leading_space = True - if index == len(scalar)-1: - trailing_space = True - if previous_break: - break_space = True - previous_space = True - previous_break = False - elif ch in '\n\x85\u2028\u2029': - if index == 0: - leading_break = True - if index == len(scalar)-1: - trailing_break = True - if previous_space: - space_break = True - previous_space = False - previous_break = True - else: - previous_space = False - previous_break = False - - # Prepare for the next character. - index += 1 - preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029') - followed_by_whitespace = (index+1 >= len(scalar) or - scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029') - - # Let's decide what styles are allowed. - allow_flow_plain = True - allow_block_plain = True - allow_single_quoted = True - allow_double_quoted = True - allow_block = True - - # Leading and trailing whitespaces are bad for plain scalars. - if (leading_space or leading_break - or trailing_space or trailing_break): - allow_flow_plain = allow_block_plain = False - - # We do not permit trailing spaces for block scalars. - if trailing_space: - allow_block = False - - # Spaces at the beginning of a new line are only acceptable for block - # scalars. - if break_space: - allow_flow_plain = allow_block_plain = allow_single_quoted = False - - # Spaces followed by breaks, as well as special character are only - # allowed for double quoted scalars. - if space_break or special_characters: - allow_flow_plain = allow_block_plain = \ - allow_single_quoted = allow_block = False - - # Although the plain scalar writer supports breaks, we never emit - # multiline plain scalars. - if line_breaks: - allow_flow_plain = allow_block_plain = False - - # Flow indicators are forbidden for flow plain scalars. - if flow_indicators: - allow_flow_plain = False - - # Block indicators are forbidden for block plain scalars. - if block_indicators: - allow_block_plain = False - - return ScalarAnalysis(scalar=scalar, - empty=False, multiline=line_breaks, - allow_flow_plain=allow_flow_plain, - allow_block_plain=allow_block_plain, - allow_single_quoted=allow_single_quoted, - allow_double_quoted=allow_double_quoted, - allow_block=allow_block) - - # Writers. - - def flush_stream(self): - if hasattr(self.stream, 'flush'): - self.stream.flush() - - def write_stream_start(self): - # Write BOM if needed. - if self.encoding and self.encoding.startswith('utf-16'): - self.stream.write('\uFEFF'.encode(self.encoding)) - - def write_stream_end(self): - self.flush_stream() - - def write_indicator(self, indicator, need_whitespace, - whitespace=False, indention=False): - if self.whitespace or not need_whitespace: - data = indicator - else: - data = ' '+indicator - self.whitespace = whitespace - self.indention = self.indention and indention - self.column += len(data) - self.open_ended = False - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - - def write_indent(self): - indent = self.indent or 0 - if not self.indention or self.column > indent \ - or (self.column == indent and not self.whitespace): - self.write_line_break() - if self.column < indent: - self.whitespace = True - data = ' '*(indent-self.column) - self.column = indent - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - - def write_line_break(self, data=None): - if data is None: - data = self.best_line_break - self.whitespace = True - self.indention = True - self.line += 1 - self.column = 0 - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - - def write_version_directive(self, version_text): - data = '%%YAML %s' % version_text - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - self.write_line_break() - - def write_tag_directive(self, handle_text, prefix_text): - data = '%%TAG %s %s' % (handle_text, prefix_text) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - self.write_line_break() - - # Scalar streams. - - def write_single_quoted(self, text, split=True): - self.write_indicator('\'', True) - spaces = False - breaks = False - start = end = 0 - while end <= len(text): - ch = None - if end < len(text): - ch = text[end] - if spaces: - if ch is None or ch != ' ': - if start+1 == end and self.column > self.best_width and split \ - and start != 0 and end != len(text): - self.write_indent() - else: - data = text[start:end] - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - start = end - elif breaks: - if ch is None or ch not in '\n\x85\u2028\u2029': - if text[start] == '\n': - self.write_line_break() - for br in text[start:end]: - if br == '\n': - self.write_line_break() - else: - self.write_line_break(br) - self.write_indent() - start = end - else: - if ch is None or ch in ' \n\x85\u2028\u2029' or ch == '\'': - if start < end: - data = text[start:end] - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - start = end - if ch == '\'': - data = '\'\'' - self.column += 2 - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - start = end + 1 - if ch is not None: - spaces = (ch == ' ') - breaks = (ch in '\n\x85\u2028\u2029') - end += 1 - self.write_indicator('\'', False) - - ESCAPE_REPLACEMENTS = { - '\0': '0', - '\x07': 'a', - '\x08': 'b', - '\x09': 't', - '\x0A': 'n', - '\x0B': 'v', - '\x0C': 'f', - '\x0D': 'r', - '\x1B': 'e', - '\"': '\"', - '\\': '\\', - '\x85': 'N', - '\xA0': '_', - '\u2028': 'L', - '\u2029': 'P', - } - - def write_double_quoted(self, text, split=True): - self.write_indicator('"', True) - start = end = 0 - while end <= len(text): - ch = None - if end < len(text): - ch = text[end] - if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \ - or not ('\x20' <= ch <= '\x7E' - or (self.allow_unicode - and ('\xA0' <= ch <= '\uD7FF' - or '\uE000' <= ch <= '\uFFFD'))): - if start < end: - data = text[start:end] - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - start = end - if ch is not None: - if ch in self.ESCAPE_REPLACEMENTS: - data = '\\'+self.ESCAPE_REPLACEMENTS[ch] - elif ch <= '\xFF': - data = '\\x%02X' % ord(ch) - elif ch <= '\uFFFF': - data = '\\u%04X' % ord(ch) - else: - data = '\\U%08X' % ord(ch) - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - start = end+1 - if 0 < end < len(text)-1 and (ch == ' ' or start >= end) \ - and self.column+(end-start) > self.best_width and split: - data = text[start:end]+'\\' - if start < end: - start = end - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - self.write_indent() - self.whitespace = False - self.indention = False - if text[start] == ' ': - data = '\\' - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - end += 1 - self.write_indicator('"', False) - - def determine_block_hints(self, text): - hints = '' - if text: - if text[0] in ' \n\x85\u2028\u2029': - hints += str(self.best_indent) - if text[-1] not in '\n\x85\u2028\u2029': - hints += '-' - elif len(text) == 1 or text[-2] in '\n\x85\u2028\u2029': - hints += '+' - return hints - - def write_folded(self, text): - hints = self.determine_block_hints(text) - self.write_indicator('>'+hints, True) - if hints[-1:] == '+': - self.open_ended = True - self.write_line_break() - leading_space = True - spaces = False - breaks = True - start = end = 0 - while end <= len(text): - ch = None - if end < len(text): - ch = text[end] - if breaks: - if ch is None or ch not in '\n\x85\u2028\u2029': - if not leading_space and ch is not None and ch != ' ' \ - and text[start] == '\n': - self.write_line_break() - leading_space = (ch == ' ') - for br in text[start:end]: - if br == '\n': - self.write_line_break() - else: - self.write_line_break(br) - if ch is not None: - self.write_indent() - start = end - elif spaces: - if ch != ' ': - if start+1 == end and self.column > self.best_width: - self.write_indent() - else: - data = text[start:end] - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - start = end - else: - if ch is None or ch in ' \n\x85\u2028\u2029': - data = text[start:end] - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - if ch is None: - self.write_line_break() - start = end - if ch is not None: - breaks = (ch in '\n\x85\u2028\u2029') - spaces = (ch == ' ') - end += 1 - - def write_literal(self, text): - hints = self.determine_block_hints(text) - self.write_indicator('|'+hints, True) - if hints[-1:] == '+': - self.open_ended = True - self.write_line_break() - breaks = True - start = end = 0 - while end <= len(text): - ch = None - if end < len(text): - ch = text[end] - if breaks: - if ch is None or ch not in '\n\x85\u2028\u2029': - for br in text[start:end]: - if br == '\n': - self.write_line_break() - else: - self.write_line_break(br) - if ch is not None: - self.write_indent() - start = end - else: - if ch is None or ch in '\n\x85\u2028\u2029': - data = text[start:end] - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - if ch is None: - self.write_line_break() - start = end - if ch is not None: - breaks = (ch in '\n\x85\u2028\u2029') - end += 1 - - def write_plain(self, text, split=True): - if self.root_context: - self.open_ended = True - if not text: - return - if not self.whitespace: - data = ' ' - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - self.whitespace = False - self.indention = False - spaces = False - breaks = False - start = end = 0 - while end <= len(text): - ch = None - if end < len(text): - ch = text[end] - if spaces: - if ch != ' ': - if start+1 == end and self.column > self.best_width and split: - self.write_indent() - self.whitespace = False - self.indention = False - else: - data = text[start:end] - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - start = end - elif breaks: - if ch not in '\n\x85\u2028\u2029': - if text[start] == '\n': - self.write_line_break() - for br in text[start:end]: - if br == '\n': - self.write_line_break() - else: - self.write_line_break(br) - self.write_indent() - self.whitespace = False - self.indention = False - start = end - else: - if ch is None or ch in ' \n\x85\u2028\u2029': - data = text[start:end] - self.column += len(data) - if self.encoding: - data = data.encode(self.encoding) - self.stream.write(data) - start = end - if ch is not None: - spaces = (ch == ' ') - breaks = (ch in '\n\x85\u2028\u2029') - end += 1 diff --git a/coredns/venv/yaml/error.py b/coredns/venv/yaml/error.py deleted file mode 100644 index b796b4d..0000000 --- a/coredns/venv/yaml/error.py +++ /dev/null @@ -1,75 +0,0 @@ - -__all__ = ['Mark', 'YAMLError', 'MarkedYAMLError'] - -class Mark: - - def __init__(self, name, index, line, column, buffer, pointer): - self.name = name - self.index = index - self.line = line - self.column = column - self.buffer = buffer - self.pointer = pointer - - def get_snippet(self, indent=4, max_length=75): - if self.buffer is None: - return None - head = '' - start = self.pointer - while start > 0 and self.buffer[start-1] not in '\0\r\n\x85\u2028\u2029': - start -= 1 - if self.pointer-start > max_length/2-1: - head = ' ... ' - start += 5 - break - tail = '' - end = self.pointer - while end < len(self.buffer) and self.buffer[end] not in '\0\r\n\x85\u2028\u2029': - end += 1 - if end-self.pointer > max_length/2-1: - tail = ' ... ' - end -= 5 - break - snippet = self.buffer[start:end] - return ' '*indent + head + snippet + tail + '\n' \ - + ' '*(indent+self.pointer-start+len(head)) + '^' - - def __str__(self): - snippet = self.get_snippet() - where = " in \"%s\", line %d, column %d" \ - % (self.name, self.line+1, self.column+1) - if snippet is not None: - where += ":\n"+snippet - return where - -class YAMLError(Exception): - pass - -class MarkedYAMLError(YAMLError): - - def __init__(self, context=None, context_mark=None, - problem=None, problem_mark=None, note=None): - self.context = context - self.context_mark = context_mark - self.problem = problem - self.problem_mark = problem_mark - self.note = note - - def __str__(self): - lines = [] - if self.context is not None: - lines.append(self.context) - if self.context_mark is not None \ - and (self.problem is None or self.problem_mark is None - or self.context_mark.name != self.problem_mark.name - or self.context_mark.line != self.problem_mark.line - or self.context_mark.column != self.problem_mark.column): - lines.append(str(self.context_mark)) - if self.problem is not None: - lines.append(self.problem) - if self.problem_mark is not None: - lines.append(str(self.problem_mark)) - if self.note is not None: - lines.append(self.note) - return '\n'.join(lines) - diff --git a/coredns/venv/yaml/events.py b/coredns/venv/yaml/events.py deleted file mode 100644 index f79ad38..0000000 --- a/coredns/venv/yaml/events.py +++ /dev/null @@ -1,86 +0,0 @@ - -# Abstract classes. - -class Event(object): - def __init__(self, start_mark=None, end_mark=None): - self.start_mark = start_mark - self.end_mark = end_mark - def __repr__(self): - attributes = [key for key in ['anchor', 'tag', 'implicit', 'value'] - if hasattr(self, key)] - arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) - for key in attributes]) - return '%s(%s)' % (self.__class__.__name__, arguments) - -class NodeEvent(Event): - def __init__(self, anchor, start_mark=None, end_mark=None): - self.anchor = anchor - self.start_mark = start_mark - self.end_mark = end_mark - -class CollectionStartEvent(NodeEvent): - def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None, - flow_style=None): - self.anchor = anchor - self.tag = tag - self.implicit = implicit - self.start_mark = start_mark - self.end_mark = end_mark - self.flow_style = flow_style - -class CollectionEndEvent(Event): - pass - -# Implementations. - -class StreamStartEvent(Event): - def __init__(self, start_mark=None, end_mark=None, encoding=None): - self.start_mark = start_mark - self.end_mark = end_mark - self.encoding = encoding - -class StreamEndEvent(Event): - pass - -class DocumentStartEvent(Event): - def __init__(self, start_mark=None, end_mark=None, - explicit=None, version=None, tags=None): - self.start_mark = start_mark - self.end_mark = end_mark - self.explicit = explicit - self.version = version - self.tags = tags - -class DocumentEndEvent(Event): - def __init__(self, start_mark=None, end_mark=None, - explicit=None): - self.start_mark = start_mark - self.end_mark = end_mark - self.explicit = explicit - -class AliasEvent(NodeEvent): - pass - -class ScalarEvent(NodeEvent): - def __init__(self, anchor, tag, implicit, value, - start_mark=None, end_mark=None, style=None): - self.anchor = anchor - self.tag = tag - self.implicit = implicit - self.value = value - self.start_mark = start_mark - self.end_mark = end_mark - self.style = style - -class SequenceStartEvent(CollectionStartEvent): - pass - -class SequenceEndEvent(CollectionEndEvent): - pass - -class MappingStartEvent(CollectionStartEvent): - pass - -class MappingEndEvent(CollectionEndEvent): - pass - diff --git a/coredns/venv/yaml/loader.py b/coredns/venv/yaml/loader.py deleted file mode 100644 index e90c112..0000000 --- a/coredns/venv/yaml/loader.py +++ /dev/null @@ -1,63 +0,0 @@ - -__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader'] - -from .reader import * -from .scanner import * -from .parser import * -from .composer import * -from .constructor import * -from .resolver import * - -class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolver): - - def __init__(self, stream): - Reader.__init__(self, stream) - Scanner.__init__(self) - Parser.__init__(self) - Composer.__init__(self) - BaseConstructor.__init__(self) - BaseResolver.__init__(self) - -class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver): - - def __init__(self, stream): - Reader.__init__(self, stream) - Scanner.__init__(self) - Parser.__init__(self) - Composer.__init__(self) - FullConstructor.__init__(self) - Resolver.__init__(self) - -class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver): - - def __init__(self, stream): - Reader.__init__(self, stream) - Scanner.__init__(self) - Parser.__init__(self) - Composer.__init__(self) - SafeConstructor.__init__(self) - Resolver.__init__(self) - -class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver): - - def __init__(self, stream): - Reader.__init__(self, stream) - Scanner.__init__(self) - Parser.__init__(self) - Composer.__init__(self) - Constructor.__init__(self) - Resolver.__init__(self) - -# UnsafeLoader is the same as Loader (which is and was always unsafe on -# untrusted input). Use of either Loader or UnsafeLoader should be rare, since -# FullLoad should be able to load almost all YAML safely. Loader is left intact -# to ensure backwards compatibility. -class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver): - - def __init__(self, stream): - Reader.__init__(self, stream) - Scanner.__init__(self) - Parser.__init__(self) - Composer.__init__(self) - Constructor.__init__(self) - Resolver.__init__(self) diff --git a/coredns/venv/yaml/nodes.py b/coredns/venv/yaml/nodes.py deleted file mode 100644 index c4f070c..0000000 --- a/coredns/venv/yaml/nodes.py +++ /dev/null @@ -1,49 +0,0 @@ - -class Node(object): - def __init__(self, tag, value, start_mark, end_mark): - self.tag = tag - self.value = value - self.start_mark = start_mark - self.end_mark = end_mark - def __repr__(self): - value = self.value - #if isinstance(value, list): - # if len(value) == 0: - # value = '' - # elif len(value) == 1: - # value = '<1 item>' - # else: - # value = '<%d items>' % len(value) - #else: - # if len(value) > 75: - # value = repr(value[:70]+u' ... ') - # else: - # value = repr(value) - value = repr(value) - return '%s(tag=%r, value=%s)' % (self.__class__.__name__, self.tag, value) - -class ScalarNode(Node): - id = 'scalar' - def __init__(self, tag, value, - start_mark=None, end_mark=None, style=None): - self.tag = tag - self.value = value - self.start_mark = start_mark - self.end_mark = end_mark - self.style = style - -class CollectionNode(Node): - def __init__(self, tag, value, - start_mark=None, end_mark=None, flow_style=None): - self.tag = tag - self.value = value - self.start_mark = start_mark - self.end_mark = end_mark - self.flow_style = flow_style - -class SequenceNode(CollectionNode): - id = 'sequence' - -class MappingNode(CollectionNode): - id = 'mapping' - diff --git a/coredns/venv/yaml/parser.py b/coredns/venv/yaml/parser.py deleted file mode 100644 index 13a5995..0000000 --- a/coredns/venv/yaml/parser.py +++ /dev/null @@ -1,589 +0,0 @@ - -# The following YAML grammar is LL(1) and is parsed by a recursive descent -# parser. -# -# stream ::= STREAM-START implicit_document? explicit_document* STREAM-END -# implicit_document ::= block_node DOCUMENT-END* -# explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* -# block_node_or_indentless_sequence ::= -# ALIAS -# | properties (block_content | indentless_block_sequence)? -# | block_content -# | indentless_block_sequence -# block_node ::= ALIAS -# | properties block_content? -# | block_content -# flow_node ::= ALIAS -# | properties flow_content? -# | flow_content -# properties ::= TAG ANCHOR? | ANCHOR TAG? -# block_content ::= block_collection | flow_collection | SCALAR -# flow_content ::= flow_collection | SCALAR -# block_collection ::= block_sequence | block_mapping -# flow_collection ::= flow_sequence | flow_mapping -# block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END -# indentless_sequence ::= (BLOCK-ENTRY block_node?)+ -# block_mapping ::= BLOCK-MAPPING_START -# ((KEY block_node_or_indentless_sequence?)? -# (VALUE block_node_or_indentless_sequence?)?)* -# BLOCK-END -# flow_sequence ::= FLOW-SEQUENCE-START -# (flow_sequence_entry FLOW-ENTRY)* -# flow_sequence_entry? -# FLOW-SEQUENCE-END -# flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -# flow_mapping ::= FLOW-MAPPING-START -# (flow_mapping_entry FLOW-ENTRY)* -# flow_mapping_entry? -# FLOW-MAPPING-END -# flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? -# -# FIRST sets: -# -# stream: { STREAM-START } -# explicit_document: { DIRECTIVE DOCUMENT-START } -# implicit_document: FIRST(block_node) -# block_node: { ALIAS TAG ANCHOR SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START } -# flow_node: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START } -# block_content: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } -# flow_content: { FLOW-SEQUENCE-START FLOW-MAPPING-START SCALAR } -# block_collection: { BLOCK-SEQUENCE-START BLOCK-MAPPING-START } -# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } -# block_sequence: { BLOCK-SEQUENCE-START } -# block_mapping: { BLOCK-MAPPING-START } -# block_node_or_indentless_sequence: { ALIAS ANCHOR TAG SCALAR BLOCK-SEQUENCE-START BLOCK-MAPPING-START FLOW-SEQUENCE-START FLOW-MAPPING-START BLOCK-ENTRY } -# indentless_sequence: { ENTRY } -# flow_collection: { FLOW-SEQUENCE-START FLOW-MAPPING-START } -# flow_sequence: { FLOW-SEQUENCE-START } -# flow_mapping: { FLOW-MAPPING-START } -# flow_sequence_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } -# flow_mapping_entry: { ALIAS ANCHOR TAG SCALAR FLOW-SEQUENCE-START FLOW-MAPPING-START KEY } - -__all__ = ['Parser', 'ParserError'] - -from .error import MarkedYAMLError -from .tokens import * -from .events import * -from .scanner import * - -class ParserError(MarkedYAMLError): - pass - -class Parser: - # Since writing a recursive-descendant parser is a straightforward task, we - # do not give many comments here. - - DEFAULT_TAGS = { - '!': '!', - '!!': 'tag:yaml.org,2002:', - } - - def __init__(self): - self.current_event = None - self.yaml_version = None - self.tag_handles = {} - self.states = [] - self.marks = [] - self.state = self.parse_stream_start - - def dispose(self): - # Reset the state attributes (to clear self-references) - self.states = [] - self.state = None - - def check_event(self, *choices): - # Check the type of the next event. - if self.current_event is None: - if self.state: - self.current_event = self.state() - if self.current_event is not None: - if not choices: - return True - for choice in choices: - if isinstance(self.current_event, choice): - return True - return False - - def peek_event(self): - # Get the next event. - if self.current_event is None: - if self.state: - self.current_event = self.state() - return self.current_event - - def get_event(self): - # Get the next event and proceed further. - if self.current_event is None: - if self.state: - self.current_event = self.state() - value = self.current_event - self.current_event = None - return value - - # stream ::= STREAM-START implicit_document? explicit_document* STREAM-END - # implicit_document ::= block_node DOCUMENT-END* - # explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* - - def parse_stream_start(self): - - # Parse the stream start. - token = self.get_token() - event = StreamStartEvent(token.start_mark, token.end_mark, - encoding=token.encoding) - - # Prepare the next state. - self.state = self.parse_implicit_document_start - - return event - - def parse_implicit_document_start(self): - - # Parse an implicit document. - if not self.check_token(DirectiveToken, DocumentStartToken, - StreamEndToken): - self.tag_handles = self.DEFAULT_TAGS - token = self.peek_token() - start_mark = end_mark = token.start_mark - event = DocumentStartEvent(start_mark, end_mark, - explicit=False) - - # Prepare the next state. - self.states.append(self.parse_document_end) - self.state = self.parse_block_node - - return event - - else: - return self.parse_document_start() - - def parse_document_start(self): - - # Parse any extra document end indicators. - while self.check_token(DocumentEndToken): - self.get_token() - - # Parse an explicit document. - if not self.check_token(StreamEndToken): - token = self.peek_token() - start_mark = token.start_mark - version, tags = self.process_directives() - if not self.check_token(DocumentStartToken): - raise ParserError(None, None, - "expected '', but found %r" - % self.peek_token().id, - self.peek_token().start_mark) - token = self.get_token() - end_mark = token.end_mark - event = DocumentStartEvent(start_mark, end_mark, - explicit=True, version=version, tags=tags) - self.states.append(self.parse_document_end) - self.state = self.parse_document_content - else: - # Parse the end of the stream. - token = self.get_token() - event = StreamEndEvent(token.start_mark, token.end_mark) - assert not self.states - assert not self.marks - self.state = None - return event - - def parse_document_end(self): - - # Parse the document end. - token = self.peek_token() - start_mark = end_mark = token.start_mark - explicit = False - if self.check_token(DocumentEndToken): - token = self.get_token() - end_mark = token.end_mark - explicit = True - event = DocumentEndEvent(start_mark, end_mark, - explicit=explicit) - - # Prepare the next state. - self.state = self.parse_document_start - - return event - - def parse_document_content(self): - if self.check_token(DirectiveToken, - DocumentStartToken, DocumentEndToken, StreamEndToken): - event = self.process_empty_scalar(self.peek_token().start_mark) - self.state = self.states.pop() - return event - else: - return self.parse_block_node() - - def process_directives(self): - self.yaml_version = None - self.tag_handles = {} - while self.check_token(DirectiveToken): - token = self.get_token() - if token.name == 'YAML': - if self.yaml_version is not None: - raise ParserError(None, None, - "found duplicate YAML directive", token.start_mark) - major, minor = token.value - if major != 1: - raise ParserError(None, None, - "found incompatible YAML document (version 1.* is required)", - token.start_mark) - self.yaml_version = token.value - elif token.name == 'TAG': - handle, prefix = token.value - if handle in self.tag_handles: - raise ParserError(None, None, - "duplicate tag handle %r" % handle, - token.start_mark) - self.tag_handles[handle] = prefix - if self.tag_handles: - value = self.yaml_version, self.tag_handles.copy() - else: - value = self.yaml_version, None - for key in self.DEFAULT_TAGS: - if key not in self.tag_handles: - self.tag_handles[key] = self.DEFAULT_TAGS[key] - return value - - # block_node_or_indentless_sequence ::= ALIAS - # | properties (block_content | indentless_block_sequence)? - # | block_content - # | indentless_block_sequence - # block_node ::= ALIAS - # | properties block_content? - # | block_content - # flow_node ::= ALIAS - # | properties flow_content? - # | flow_content - # properties ::= TAG ANCHOR? | ANCHOR TAG? - # block_content ::= block_collection | flow_collection | SCALAR - # flow_content ::= flow_collection | SCALAR - # block_collection ::= block_sequence | block_mapping - # flow_collection ::= flow_sequence | flow_mapping - - def parse_block_node(self): - return self.parse_node(block=True) - - def parse_flow_node(self): - return self.parse_node() - - def parse_block_node_or_indentless_sequence(self): - return self.parse_node(block=True, indentless_sequence=True) - - def parse_node(self, block=False, indentless_sequence=False): - if self.check_token(AliasToken): - token = self.get_token() - event = AliasEvent(token.value, token.start_mark, token.end_mark) - self.state = self.states.pop() - else: - anchor = None - tag = None - start_mark = end_mark = tag_mark = None - if self.check_token(AnchorToken): - token = self.get_token() - start_mark = token.start_mark - end_mark = token.end_mark - anchor = token.value - if self.check_token(TagToken): - token = self.get_token() - tag_mark = token.start_mark - end_mark = token.end_mark - tag = token.value - elif self.check_token(TagToken): - token = self.get_token() - start_mark = tag_mark = token.start_mark - end_mark = token.end_mark - tag = token.value - if self.check_token(AnchorToken): - token = self.get_token() - end_mark = token.end_mark - anchor = token.value - if tag is not None: - handle, suffix = tag - if handle is not None: - if handle not in self.tag_handles: - raise ParserError("while parsing a node", start_mark, - "found undefined tag handle %r" % handle, - tag_mark) - tag = self.tag_handles[handle]+suffix - else: - tag = suffix - #if tag == '!': - # raise ParserError("while parsing a node", start_mark, - # "found non-specific tag '!'", tag_mark, - # "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag' and share your opinion.") - if start_mark is None: - start_mark = end_mark = self.peek_token().start_mark - event = None - implicit = (tag is None or tag == '!') - if indentless_sequence and self.check_token(BlockEntryToken): - end_mark = self.peek_token().end_mark - event = SequenceStartEvent(anchor, tag, implicit, - start_mark, end_mark) - self.state = self.parse_indentless_sequence_entry - else: - if self.check_token(ScalarToken): - token = self.get_token() - end_mark = token.end_mark - if (token.plain and tag is None) or tag == '!': - implicit = (True, False) - elif tag is None: - implicit = (False, True) - else: - implicit = (False, False) - event = ScalarEvent(anchor, tag, implicit, token.value, - start_mark, end_mark, style=token.style) - self.state = self.states.pop() - elif self.check_token(FlowSequenceStartToken): - end_mark = self.peek_token().end_mark - event = SequenceStartEvent(anchor, tag, implicit, - start_mark, end_mark, flow_style=True) - self.state = self.parse_flow_sequence_first_entry - elif self.check_token(FlowMappingStartToken): - end_mark = self.peek_token().end_mark - event = MappingStartEvent(anchor, tag, implicit, - start_mark, end_mark, flow_style=True) - self.state = self.parse_flow_mapping_first_key - elif block and self.check_token(BlockSequenceStartToken): - end_mark = self.peek_token().start_mark - event = SequenceStartEvent(anchor, tag, implicit, - start_mark, end_mark, flow_style=False) - self.state = self.parse_block_sequence_first_entry - elif block and self.check_token(BlockMappingStartToken): - end_mark = self.peek_token().start_mark - event = MappingStartEvent(anchor, tag, implicit, - start_mark, end_mark, flow_style=False) - self.state = self.parse_block_mapping_first_key - elif anchor is not None or tag is not None: - # Empty scalars are allowed even if a tag or an anchor is - # specified. - event = ScalarEvent(anchor, tag, (implicit, False), '', - start_mark, end_mark) - self.state = self.states.pop() - else: - if block: - node = 'block' - else: - node = 'flow' - token = self.peek_token() - raise ParserError("while parsing a %s node" % node, start_mark, - "expected the node content, but found %r" % token.id, - token.start_mark) - return event - - # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END - - def parse_block_sequence_first_entry(self): - token = self.get_token() - self.marks.append(token.start_mark) - return self.parse_block_sequence_entry() - - def parse_block_sequence_entry(self): - if self.check_token(BlockEntryToken): - token = self.get_token() - if not self.check_token(BlockEntryToken, BlockEndToken): - self.states.append(self.parse_block_sequence_entry) - return self.parse_block_node() - else: - self.state = self.parse_block_sequence_entry - return self.process_empty_scalar(token.end_mark) - if not self.check_token(BlockEndToken): - token = self.peek_token() - raise ParserError("while parsing a block collection", self.marks[-1], - "expected , but found %r" % token.id, token.start_mark) - token = self.get_token() - event = SequenceEndEvent(token.start_mark, token.end_mark) - self.state = self.states.pop() - self.marks.pop() - return event - - # indentless_sequence ::= (BLOCK-ENTRY block_node?)+ - - def parse_indentless_sequence_entry(self): - if self.check_token(BlockEntryToken): - token = self.get_token() - if not self.check_token(BlockEntryToken, - KeyToken, ValueToken, BlockEndToken): - self.states.append(self.parse_indentless_sequence_entry) - return self.parse_block_node() - else: - self.state = self.parse_indentless_sequence_entry - return self.process_empty_scalar(token.end_mark) - token = self.peek_token() - event = SequenceEndEvent(token.start_mark, token.start_mark) - self.state = self.states.pop() - return event - - # block_mapping ::= BLOCK-MAPPING_START - # ((KEY block_node_or_indentless_sequence?)? - # (VALUE block_node_or_indentless_sequence?)?)* - # BLOCK-END - - def parse_block_mapping_first_key(self): - token = self.get_token() - self.marks.append(token.start_mark) - return self.parse_block_mapping_key() - - def parse_block_mapping_key(self): - if self.check_token(KeyToken): - token = self.get_token() - if not self.check_token(KeyToken, ValueToken, BlockEndToken): - self.states.append(self.parse_block_mapping_value) - return self.parse_block_node_or_indentless_sequence() - else: - self.state = self.parse_block_mapping_value - return self.process_empty_scalar(token.end_mark) - if not self.check_token(BlockEndToken): - token = self.peek_token() - raise ParserError("while parsing a block mapping", self.marks[-1], - "expected , but found %r" % token.id, token.start_mark) - token = self.get_token() - event = MappingEndEvent(token.start_mark, token.end_mark) - self.state = self.states.pop() - self.marks.pop() - return event - - def parse_block_mapping_value(self): - if self.check_token(ValueToken): - token = self.get_token() - if not self.check_token(KeyToken, ValueToken, BlockEndToken): - self.states.append(self.parse_block_mapping_key) - return self.parse_block_node_or_indentless_sequence() - else: - self.state = self.parse_block_mapping_key - return self.process_empty_scalar(token.end_mark) - else: - self.state = self.parse_block_mapping_key - token = self.peek_token() - return self.process_empty_scalar(token.start_mark) - - # flow_sequence ::= FLOW-SEQUENCE-START - # (flow_sequence_entry FLOW-ENTRY)* - # flow_sequence_entry? - # FLOW-SEQUENCE-END - # flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? - # - # Note that while production rules for both flow_sequence_entry and - # flow_mapping_entry are equal, their interpretations are different. - # For `flow_sequence_entry`, the part `KEY flow_node? (VALUE flow_node?)?` - # generate an inline mapping (set syntax). - - def parse_flow_sequence_first_entry(self): - token = self.get_token() - self.marks.append(token.start_mark) - return self.parse_flow_sequence_entry(first=True) - - def parse_flow_sequence_entry(self, first=False): - if not self.check_token(FlowSequenceEndToken): - if not first: - if self.check_token(FlowEntryToken): - self.get_token() - else: - token = self.peek_token() - raise ParserError("while parsing a flow sequence", self.marks[-1], - "expected ',' or ']', but got %r" % token.id, token.start_mark) - - if self.check_token(KeyToken): - token = self.peek_token() - event = MappingStartEvent(None, None, True, - token.start_mark, token.end_mark, - flow_style=True) - self.state = self.parse_flow_sequence_entry_mapping_key - return event - elif not self.check_token(FlowSequenceEndToken): - self.states.append(self.parse_flow_sequence_entry) - return self.parse_flow_node() - token = self.get_token() - event = SequenceEndEvent(token.start_mark, token.end_mark) - self.state = self.states.pop() - self.marks.pop() - return event - - def parse_flow_sequence_entry_mapping_key(self): - token = self.get_token() - if not self.check_token(ValueToken, - FlowEntryToken, FlowSequenceEndToken): - self.states.append(self.parse_flow_sequence_entry_mapping_value) - return self.parse_flow_node() - else: - self.state = self.parse_flow_sequence_entry_mapping_value - return self.process_empty_scalar(token.end_mark) - - def parse_flow_sequence_entry_mapping_value(self): - if self.check_token(ValueToken): - token = self.get_token() - if not self.check_token(FlowEntryToken, FlowSequenceEndToken): - self.states.append(self.parse_flow_sequence_entry_mapping_end) - return self.parse_flow_node() - else: - self.state = self.parse_flow_sequence_entry_mapping_end - return self.process_empty_scalar(token.end_mark) - else: - self.state = self.parse_flow_sequence_entry_mapping_end - token = self.peek_token() - return self.process_empty_scalar(token.start_mark) - - def parse_flow_sequence_entry_mapping_end(self): - self.state = self.parse_flow_sequence_entry - token = self.peek_token() - return MappingEndEvent(token.start_mark, token.start_mark) - - # flow_mapping ::= FLOW-MAPPING-START - # (flow_mapping_entry FLOW-ENTRY)* - # flow_mapping_entry? - # FLOW-MAPPING-END - # flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? - - def parse_flow_mapping_first_key(self): - token = self.get_token() - self.marks.append(token.start_mark) - return self.parse_flow_mapping_key(first=True) - - def parse_flow_mapping_key(self, first=False): - if not self.check_token(FlowMappingEndToken): - if not first: - if self.check_token(FlowEntryToken): - self.get_token() - else: - token = self.peek_token() - raise ParserError("while parsing a flow mapping", self.marks[-1], - "expected ',' or '}', but got %r" % token.id, token.start_mark) - if self.check_token(KeyToken): - token = self.get_token() - if not self.check_token(ValueToken, - FlowEntryToken, FlowMappingEndToken): - self.states.append(self.parse_flow_mapping_value) - return self.parse_flow_node() - else: - self.state = self.parse_flow_mapping_value - return self.process_empty_scalar(token.end_mark) - elif not self.check_token(FlowMappingEndToken): - self.states.append(self.parse_flow_mapping_empty_value) - return self.parse_flow_node() - token = self.get_token() - event = MappingEndEvent(token.start_mark, token.end_mark) - self.state = self.states.pop() - self.marks.pop() - return event - - def parse_flow_mapping_value(self): - if self.check_token(ValueToken): - token = self.get_token() - if not self.check_token(FlowEntryToken, FlowMappingEndToken): - self.states.append(self.parse_flow_mapping_key) - return self.parse_flow_node() - else: - self.state = self.parse_flow_mapping_key - return self.process_empty_scalar(token.end_mark) - else: - self.state = self.parse_flow_mapping_key - token = self.peek_token() - return self.process_empty_scalar(token.start_mark) - - def parse_flow_mapping_empty_value(self): - self.state = self.parse_flow_mapping_key - return self.process_empty_scalar(self.peek_token().start_mark) - - def process_empty_scalar(self, mark): - return ScalarEvent(None, None, (True, False), '', mark, mark) - diff --git a/coredns/venv/yaml/reader.py b/coredns/venv/yaml/reader.py deleted file mode 100644 index 774b021..0000000 --- a/coredns/venv/yaml/reader.py +++ /dev/null @@ -1,185 +0,0 @@ -# This module contains abstractions for the input stream. You don't have to -# looks further, there are no pretty code. -# -# We define two classes here. -# -# Mark(source, line, column) -# It's just a record and its only use is producing nice error messages. -# Parser does not use it for any other purposes. -# -# Reader(source, data) -# Reader determines the encoding of `data` and converts it to unicode. -# Reader provides the following methods and attributes: -# reader.peek(length=1) - return the next `length` characters -# reader.forward(length=1) - move the current position to `length` characters. -# reader.index - the number of the current character. -# reader.line, stream.column - the line and the column of the current character. - -__all__ = ['Reader', 'ReaderError'] - -from .error import YAMLError, Mark - -import codecs, re - -class ReaderError(YAMLError): - - def __init__(self, name, position, character, encoding, reason): - self.name = name - self.character = character - self.position = position - self.encoding = encoding - self.reason = reason - - def __str__(self): - if isinstance(self.character, bytes): - return "'%s' codec can't decode byte #x%02x: %s\n" \ - " in \"%s\", position %d" \ - % (self.encoding, ord(self.character), self.reason, - self.name, self.position) - else: - return "unacceptable character #x%04x: %s\n" \ - " in \"%s\", position %d" \ - % (self.character, self.reason, - self.name, self.position) - -class Reader(object): - # Reader: - # - determines the data encoding and converts it to a unicode string, - # - checks if characters are in allowed range, - # - adds '\0' to the end. - - # Reader accepts - # - a `bytes` object, - # - a `str` object, - # - a file-like object with its `read` method returning `str`, - # - a file-like object with its `read` method returning `unicode`. - - # Yeah, it's ugly and slow. - - def __init__(self, stream): - self.name = None - self.stream = None - self.stream_pointer = 0 - self.eof = True - self.buffer = '' - self.pointer = 0 - self.raw_buffer = None - self.raw_decode = None - self.encoding = None - self.index = 0 - self.line = 0 - self.column = 0 - if isinstance(stream, str): - self.name = "" - self.check_printable(stream) - self.buffer = stream+'\0' - elif isinstance(stream, bytes): - self.name = "" - self.raw_buffer = stream - self.determine_encoding() - else: - self.stream = stream - self.name = getattr(stream, 'name', "") - self.eof = False - self.raw_buffer = None - self.determine_encoding() - - def peek(self, index=0): - try: - return self.buffer[self.pointer+index] - except IndexError: - self.update(index+1) - return self.buffer[self.pointer+index] - - def prefix(self, length=1): - if self.pointer+length >= len(self.buffer): - self.update(length) - return self.buffer[self.pointer:self.pointer+length] - - def forward(self, length=1): - if self.pointer+length+1 >= len(self.buffer): - self.update(length+1) - while length: - ch = self.buffer[self.pointer] - self.pointer += 1 - self.index += 1 - if ch in '\n\x85\u2028\u2029' \ - or (ch == '\r' and self.buffer[self.pointer] != '\n'): - self.line += 1 - self.column = 0 - elif ch != '\uFEFF': - self.column += 1 - length -= 1 - - def get_mark(self): - if self.stream is None: - return Mark(self.name, self.index, self.line, self.column, - self.buffer, self.pointer) - else: - return Mark(self.name, self.index, self.line, self.column, - None, None) - - def determine_encoding(self): - while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2): - self.update_raw() - if isinstance(self.raw_buffer, bytes): - if self.raw_buffer.startswith(codecs.BOM_UTF16_LE): - self.raw_decode = codecs.utf_16_le_decode - self.encoding = 'utf-16-le' - elif self.raw_buffer.startswith(codecs.BOM_UTF16_BE): - self.raw_decode = codecs.utf_16_be_decode - self.encoding = 'utf-16-be' - else: - self.raw_decode = codecs.utf_8_decode - self.encoding = 'utf-8' - self.update(1) - - NON_PRINTABLE = re.compile('[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]') - def check_printable(self, data): - match = self.NON_PRINTABLE.search(data) - if match: - character = match.group() - position = self.index+(len(self.buffer)-self.pointer)+match.start() - raise ReaderError(self.name, position, ord(character), - 'unicode', "special characters are not allowed") - - def update(self, length): - if self.raw_buffer is None: - return - self.buffer = self.buffer[self.pointer:] - self.pointer = 0 - while len(self.buffer) < length: - if not self.eof: - self.update_raw() - if self.raw_decode is not None: - try: - data, converted = self.raw_decode(self.raw_buffer, - 'strict', self.eof) - except UnicodeDecodeError as exc: - character = self.raw_buffer[exc.start] - if self.stream is not None: - position = self.stream_pointer-len(self.raw_buffer)+exc.start - else: - position = exc.start - raise ReaderError(self.name, position, character, - exc.encoding, exc.reason) - else: - data = self.raw_buffer - converted = len(data) - self.check_printable(data) - self.buffer += data - self.raw_buffer = self.raw_buffer[converted:] - if self.eof: - self.buffer += '\0' - self.raw_buffer = None - break - - def update_raw(self, size=4096): - data = self.stream.read(size) - if self.raw_buffer is None: - self.raw_buffer = data - else: - self.raw_buffer += data - self.stream_pointer += len(data) - if not data: - self.eof = True diff --git a/coredns/venv/yaml/representer.py b/coredns/venv/yaml/representer.py deleted file mode 100644 index 3b0b192..0000000 --- a/coredns/venv/yaml/representer.py +++ /dev/null @@ -1,389 +0,0 @@ - -__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', - 'RepresenterError'] - -from .error import * -from .nodes import * - -import datetime, copyreg, types, base64, collections - -class RepresenterError(YAMLError): - pass - -class BaseRepresenter: - - yaml_representers = {} - yaml_multi_representers = {} - - def __init__(self, default_style=None, default_flow_style=False, sort_keys=True): - self.default_style = default_style - self.sort_keys = sort_keys - self.default_flow_style = default_flow_style - self.represented_objects = {} - self.object_keeper = [] - self.alias_key = None - - def represent(self, data): - node = self.represent_data(data) - self.serialize(node) - self.represented_objects = {} - self.object_keeper = [] - self.alias_key = None - - def represent_data(self, data): - if self.ignore_aliases(data): - self.alias_key = None - else: - self.alias_key = id(data) - if self.alias_key is not None: - if self.alias_key in self.represented_objects: - node = self.represented_objects[self.alias_key] - #if node is None: - # raise RepresenterError("recursive objects are not allowed: %r" % data) - return node - #self.represented_objects[alias_key] = None - self.object_keeper.append(data) - data_types = type(data).__mro__ - if data_types[0] in self.yaml_representers: - node = self.yaml_representers[data_types[0]](self, data) - else: - for data_type in data_types: - if data_type in self.yaml_multi_representers: - node = self.yaml_multi_representers[data_type](self, data) - break - else: - if None in self.yaml_multi_representers: - node = self.yaml_multi_representers[None](self, data) - elif None in self.yaml_representers: - node = self.yaml_representers[None](self, data) - else: - node = ScalarNode(None, str(data)) - #if alias_key is not None: - # self.represented_objects[alias_key] = node - return node - - @classmethod - def add_representer(cls, data_type, representer): - if not 'yaml_representers' in cls.__dict__: - cls.yaml_representers = cls.yaml_representers.copy() - cls.yaml_representers[data_type] = representer - - @classmethod - def add_multi_representer(cls, data_type, representer): - if not 'yaml_multi_representers' in cls.__dict__: - cls.yaml_multi_representers = cls.yaml_multi_representers.copy() - cls.yaml_multi_representers[data_type] = representer - - def represent_scalar(self, tag, value, style=None): - if style is None: - style = self.default_style - node = ScalarNode(tag, value, style=style) - if self.alias_key is not None: - self.represented_objects[self.alias_key] = node - return node - - def represent_sequence(self, tag, sequence, flow_style=None): - value = [] - node = SequenceNode(tag, value, flow_style=flow_style) - if self.alias_key is not None: - self.represented_objects[self.alias_key] = node - best_style = True - for item in sequence: - node_item = self.represent_data(item) - if not (isinstance(node_item, ScalarNode) and not node_item.style): - best_style = False - value.append(node_item) - if flow_style is None: - if self.default_flow_style is not None: - node.flow_style = self.default_flow_style - else: - node.flow_style = best_style - return node - - def represent_mapping(self, tag, mapping, flow_style=None): - value = [] - node = MappingNode(tag, value, flow_style=flow_style) - if self.alias_key is not None: - self.represented_objects[self.alias_key] = node - best_style = True - if hasattr(mapping, 'items'): - mapping = list(mapping.items()) - if self.sort_keys: - try: - mapping = sorted(mapping) - except TypeError: - pass - for item_key, item_value in mapping: - node_key = self.represent_data(item_key) - node_value = self.represent_data(item_value) - if not (isinstance(node_key, ScalarNode) and not node_key.style): - best_style = False - if not (isinstance(node_value, ScalarNode) and not node_value.style): - best_style = False - value.append((node_key, node_value)) - if flow_style is None: - if self.default_flow_style is not None: - node.flow_style = self.default_flow_style - else: - node.flow_style = best_style - return node - - def ignore_aliases(self, data): - return False - -class SafeRepresenter(BaseRepresenter): - - def ignore_aliases(self, data): - if data is None: - return True - if isinstance(data, tuple) and data == (): - return True - if isinstance(data, (str, bytes, bool, int, float)): - return True - - def represent_none(self, data): - return self.represent_scalar('tag:yaml.org,2002:null', 'null') - - def represent_str(self, data): - return self.represent_scalar('tag:yaml.org,2002:str', data) - - def represent_binary(self, data): - if hasattr(base64, 'encodebytes'): - data = base64.encodebytes(data).decode('ascii') - else: - data = base64.encodestring(data).decode('ascii') - return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|') - - def represent_bool(self, data): - if data: - value = 'true' - else: - value = 'false' - return self.represent_scalar('tag:yaml.org,2002:bool', value) - - def represent_int(self, data): - return self.represent_scalar('tag:yaml.org,2002:int', str(data)) - - inf_value = 1e300 - while repr(inf_value) != repr(inf_value*inf_value): - inf_value *= inf_value - - def represent_float(self, data): - if data != data or (data == 0.0 and data == 1.0): - value = '.nan' - elif data == self.inf_value: - value = '.inf' - elif data == -self.inf_value: - value = '-.inf' - else: - value = repr(data).lower() - # Note that in some cases `repr(data)` represents a float number - # without the decimal parts. For instance: - # >>> repr(1e17) - # '1e17' - # Unfortunately, this is not a valid float representation according - # to the definition of the `!!float` tag. We fix this by adding - # '.0' before the 'e' symbol. - if '.' not in value and 'e' in value: - value = value.replace('e', '.0e', 1) - return self.represent_scalar('tag:yaml.org,2002:float', value) - - def represent_list(self, data): - #pairs = (len(data) > 0 and isinstance(data, list)) - #if pairs: - # for item in data: - # if not isinstance(item, tuple) or len(item) != 2: - # pairs = False - # break - #if not pairs: - return self.represent_sequence('tag:yaml.org,2002:seq', data) - #value = [] - #for item_key, item_value in data: - # value.append(self.represent_mapping(u'tag:yaml.org,2002:map', - # [(item_key, item_value)])) - #return SequenceNode(u'tag:yaml.org,2002:pairs', value) - - def represent_dict(self, data): - return self.represent_mapping('tag:yaml.org,2002:map', data) - - def represent_set(self, data): - value = {} - for key in data: - value[key] = None - return self.represent_mapping('tag:yaml.org,2002:set', value) - - def represent_date(self, data): - value = data.isoformat() - return self.represent_scalar('tag:yaml.org,2002:timestamp', value) - - def represent_datetime(self, data): - value = data.isoformat(' ') - return self.represent_scalar('tag:yaml.org,2002:timestamp', value) - - def represent_yaml_object(self, tag, data, cls, flow_style=None): - if hasattr(data, '__getstate__'): - state = data.__getstate__() - else: - state = data.__dict__.copy() - return self.represent_mapping(tag, state, flow_style=flow_style) - - def represent_undefined(self, data): - raise RepresenterError("cannot represent an object", data) - -SafeRepresenter.add_representer(type(None), - SafeRepresenter.represent_none) - -SafeRepresenter.add_representer(str, - SafeRepresenter.represent_str) - -SafeRepresenter.add_representer(bytes, - SafeRepresenter.represent_binary) - -SafeRepresenter.add_representer(bool, - SafeRepresenter.represent_bool) - -SafeRepresenter.add_representer(int, - SafeRepresenter.represent_int) - -SafeRepresenter.add_representer(float, - SafeRepresenter.represent_float) - -SafeRepresenter.add_representer(list, - SafeRepresenter.represent_list) - -SafeRepresenter.add_representer(tuple, - SafeRepresenter.represent_list) - -SafeRepresenter.add_representer(dict, - SafeRepresenter.represent_dict) - -SafeRepresenter.add_representer(set, - SafeRepresenter.represent_set) - -SafeRepresenter.add_representer(datetime.date, - SafeRepresenter.represent_date) - -SafeRepresenter.add_representer(datetime.datetime, - SafeRepresenter.represent_datetime) - -SafeRepresenter.add_representer(None, - SafeRepresenter.represent_undefined) - -class Representer(SafeRepresenter): - - def represent_complex(self, data): - if data.imag == 0.0: - data = '%r' % data.real - elif data.real == 0.0: - data = '%rj' % data.imag - elif data.imag > 0: - data = '%r+%rj' % (data.real, data.imag) - else: - data = '%r%rj' % (data.real, data.imag) - return self.represent_scalar('tag:yaml.org,2002:python/complex', data) - - def represent_tuple(self, data): - return self.represent_sequence('tag:yaml.org,2002:python/tuple', data) - - def represent_name(self, data): - name = '%s.%s' % (data.__module__, data.__name__) - return self.represent_scalar('tag:yaml.org,2002:python/name:'+name, '') - - def represent_module(self, data): - return self.represent_scalar( - 'tag:yaml.org,2002:python/module:'+data.__name__, '') - - def represent_object(self, data): - # We use __reduce__ API to save the data. data.__reduce__ returns - # a tuple of length 2-5: - # (function, args, state, listitems, dictitems) - - # For reconstructing, we calls function(*args), then set its state, - # listitems, and dictitems if they are not None. - - # A special case is when function.__name__ == '__newobj__'. In this - # case we create the object with args[0].__new__(*args). - - # Another special case is when __reduce__ returns a string - we don't - # support it. - - # We produce a !!python/object, !!python/object/new or - # !!python/object/apply node. - - cls = type(data) - if cls in copyreg.dispatch_table: - reduce = copyreg.dispatch_table[cls](data) - elif hasattr(data, '__reduce_ex__'): - reduce = data.__reduce_ex__(2) - elif hasattr(data, '__reduce__'): - reduce = data.__reduce__() - else: - raise RepresenterError("cannot represent an object", data) - reduce = (list(reduce)+[None]*5)[:5] - function, args, state, listitems, dictitems = reduce - args = list(args) - if state is None: - state = {} - if listitems is not None: - listitems = list(listitems) - if dictitems is not None: - dictitems = dict(dictitems) - if function.__name__ == '__newobj__': - function = args[0] - args = args[1:] - tag = 'tag:yaml.org,2002:python/object/new:' - newobj = True - else: - tag = 'tag:yaml.org,2002:python/object/apply:' - newobj = False - function_name = '%s.%s' % (function.__module__, function.__name__) - if not args and not listitems and not dictitems \ - and isinstance(state, dict) and newobj: - return self.represent_mapping( - 'tag:yaml.org,2002:python/object:'+function_name, state) - if not listitems and not dictitems \ - and isinstance(state, dict) and not state: - return self.represent_sequence(tag+function_name, args) - value = {} - if args: - value['args'] = args - if state or not isinstance(state, dict): - value['state'] = state - if listitems: - value['listitems'] = listitems - if dictitems: - value['dictitems'] = dictitems - return self.represent_mapping(tag+function_name, value) - - def represent_ordered_dict(self, data): - # Provide uniform representation across different Python versions. - data_type = type(data) - tag = 'tag:yaml.org,2002:python/object/apply:%s.%s' \ - % (data_type.__module__, data_type.__name__) - items = [[key, value] for key, value in data.items()] - return self.represent_sequence(tag, [items]) - -Representer.add_representer(complex, - Representer.represent_complex) - -Representer.add_representer(tuple, - Representer.represent_tuple) - -Representer.add_representer(type, - Representer.represent_name) - -Representer.add_representer(collections.OrderedDict, - Representer.represent_ordered_dict) - -Representer.add_representer(types.FunctionType, - Representer.represent_name) - -Representer.add_representer(types.BuiltinFunctionType, - Representer.represent_name) - -Representer.add_representer(types.ModuleType, - Representer.represent_module) - -Representer.add_multi_representer(object, - Representer.represent_object) - diff --git a/coredns/venv/yaml/resolver.py b/coredns/venv/yaml/resolver.py deleted file mode 100644 index 02b82e7..0000000 --- a/coredns/venv/yaml/resolver.py +++ /dev/null @@ -1,227 +0,0 @@ - -__all__ = ['BaseResolver', 'Resolver'] - -from .error import * -from .nodes import * - -import re - -class ResolverError(YAMLError): - pass - -class BaseResolver: - - DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str' - DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq' - DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map' - - yaml_implicit_resolvers = {} - yaml_path_resolvers = {} - - def __init__(self): - self.resolver_exact_paths = [] - self.resolver_prefix_paths = [] - - @classmethod - def add_implicit_resolver(cls, tag, regexp, first): - if not 'yaml_implicit_resolvers' in cls.__dict__: - implicit_resolvers = {} - for key in cls.yaml_implicit_resolvers: - implicit_resolvers[key] = cls.yaml_implicit_resolvers[key][:] - cls.yaml_implicit_resolvers = implicit_resolvers - if first is None: - first = [None] - for ch in first: - cls.yaml_implicit_resolvers.setdefault(ch, []).append((tag, regexp)) - - @classmethod - def add_path_resolver(cls, tag, path, kind=None): - # Note: `add_path_resolver` is experimental. The API could be changed. - # `new_path` is a pattern that is matched against the path from the - # root to the node that is being considered. `node_path` elements are - # tuples `(node_check, index_check)`. `node_check` is a node class: - # `ScalarNode`, `SequenceNode`, `MappingNode` or `None`. `None` - # matches any kind of a node. `index_check` could be `None`, a boolean - # value, a string value, or a number. `None` and `False` match against - # any _value_ of sequence and mapping nodes. `True` matches against - # any _key_ of a mapping node. A string `index_check` matches against - # a mapping value that corresponds to a scalar key which content is - # equal to the `index_check` value. An integer `index_check` matches - # against a sequence value with the index equal to `index_check`. - if not 'yaml_path_resolvers' in cls.__dict__: - cls.yaml_path_resolvers = cls.yaml_path_resolvers.copy() - new_path = [] - for element in path: - if isinstance(element, (list, tuple)): - if len(element) == 2: - node_check, index_check = element - elif len(element) == 1: - node_check = element[0] - index_check = True - else: - raise ResolverError("Invalid path element: %s" % element) - else: - node_check = None - index_check = element - if node_check is str: - node_check = ScalarNode - elif node_check is list: - node_check = SequenceNode - elif node_check is dict: - node_check = MappingNode - elif node_check not in [ScalarNode, SequenceNode, MappingNode] \ - and not isinstance(node_check, str) \ - and node_check is not None: - raise ResolverError("Invalid node checker: %s" % node_check) - if not isinstance(index_check, (str, int)) \ - and index_check is not None: - raise ResolverError("Invalid index checker: %s" % index_check) - new_path.append((node_check, index_check)) - if kind is str: - kind = ScalarNode - elif kind is list: - kind = SequenceNode - elif kind is dict: - kind = MappingNode - elif kind not in [ScalarNode, SequenceNode, MappingNode] \ - and kind is not None: - raise ResolverError("Invalid node kind: %s" % kind) - cls.yaml_path_resolvers[tuple(new_path), kind] = tag - - def descend_resolver(self, current_node, current_index): - if not self.yaml_path_resolvers: - return - exact_paths = {} - prefix_paths = [] - if current_node: - depth = len(self.resolver_prefix_paths) - for path, kind in self.resolver_prefix_paths[-1]: - if self.check_resolver_prefix(depth, path, kind, - current_node, current_index): - if len(path) > depth: - prefix_paths.append((path, kind)) - else: - exact_paths[kind] = self.yaml_path_resolvers[path, kind] - else: - for path, kind in self.yaml_path_resolvers: - if not path: - exact_paths[kind] = self.yaml_path_resolvers[path, kind] - else: - prefix_paths.append((path, kind)) - self.resolver_exact_paths.append(exact_paths) - self.resolver_prefix_paths.append(prefix_paths) - - def ascend_resolver(self): - if not self.yaml_path_resolvers: - return - self.resolver_exact_paths.pop() - self.resolver_prefix_paths.pop() - - def check_resolver_prefix(self, depth, path, kind, - current_node, current_index): - node_check, index_check = path[depth-1] - if isinstance(node_check, str): - if current_node.tag != node_check: - return - elif node_check is not None: - if not isinstance(current_node, node_check): - return - if index_check is True and current_index is not None: - return - if (index_check is False or index_check is None) \ - and current_index is None: - return - if isinstance(index_check, str): - if not (isinstance(current_index, ScalarNode) - and index_check == current_index.value): - return - elif isinstance(index_check, int) and not isinstance(index_check, bool): - if index_check != current_index: - return - return True - - def resolve(self, kind, value, implicit): - if kind is ScalarNode and implicit[0]: - if value == '': - resolvers = self.yaml_implicit_resolvers.get('', []) - else: - resolvers = self.yaml_implicit_resolvers.get(value[0], []) - resolvers += self.yaml_implicit_resolvers.get(None, []) - for tag, regexp in resolvers: - if regexp.match(value): - return tag - implicit = implicit[1] - if self.yaml_path_resolvers: - exact_paths = self.resolver_exact_paths[-1] - if kind in exact_paths: - return exact_paths[kind] - if None in exact_paths: - return exact_paths[None] - if kind is ScalarNode: - return self.DEFAULT_SCALAR_TAG - elif kind is SequenceNode: - return self.DEFAULT_SEQUENCE_TAG - elif kind is MappingNode: - return self.DEFAULT_MAPPING_TAG - -class Resolver(BaseResolver): - pass - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:bool', - re.compile(r'''^(?:yes|Yes|YES|no|No|NO - |true|True|TRUE|false|False|FALSE - |on|On|ON|off|Off|OFF)$''', re.X), - list('yYnNtTfFoO')) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:float', - re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? - |\.[0-9_]+(?:[eE][-+][0-9]+)? - |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* - |[-+]?\.(?:inf|Inf|INF) - |\.(?:nan|NaN|NAN))$''', re.X), - list('-+0123456789.')) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:int', - re.compile(r'''^(?:[-+]?0b[0-1_]+ - |[-+]?0[0-7_]+ - |[-+]?(?:0|[1-9][0-9_]*) - |[-+]?0x[0-9a-fA-F_]+ - |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), - list('-+0123456789')) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:merge', - re.compile(r'^(?:<<)$'), - ['<']) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:null', - re.compile(r'''^(?: ~ - |null|Null|NULL - | )$''', re.X), - ['~', 'n', 'N', '']) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:timestamp', - re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] - |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]? - (?:[Tt]|[ \t]+)[0-9][0-9]? - :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)? - (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X), - list('0123456789')) - -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:value', - re.compile(r'^(?:=)$'), - ['=']) - -# The following resolver is only for documentation purposes. It cannot work -# because plain scalars cannot start with '!', '&', or '*'. -Resolver.add_implicit_resolver( - 'tag:yaml.org,2002:yaml', - re.compile(r'^(?:!|&|\*)$'), - list('!&*')) - diff --git a/coredns/venv/yaml/scanner.py b/coredns/venv/yaml/scanner.py deleted file mode 100644 index 7437ede..0000000 --- a/coredns/venv/yaml/scanner.py +++ /dev/null @@ -1,1435 +0,0 @@ - -# Scanner produces tokens of the following types: -# STREAM-START -# STREAM-END -# DIRECTIVE(name, value) -# DOCUMENT-START -# DOCUMENT-END -# BLOCK-SEQUENCE-START -# BLOCK-MAPPING-START -# BLOCK-END -# FLOW-SEQUENCE-START -# FLOW-MAPPING-START -# FLOW-SEQUENCE-END -# FLOW-MAPPING-END -# BLOCK-ENTRY -# FLOW-ENTRY -# KEY -# VALUE -# ALIAS(value) -# ANCHOR(value) -# TAG(value) -# SCALAR(value, plain, style) -# -# Read comments in the Scanner code for more details. -# - -__all__ = ['Scanner', 'ScannerError'] - -from .error import MarkedYAMLError -from .tokens import * - -class ScannerError(MarkedYAMLError): - pass - -class SimpleKey: - # See below simple keys treatment. - - def __init__(self, token_number, required, index, line, column, mark): - self.token_number = token_number - self.required = required - self.index = index - self.line = line - self.column = column - self.mark = mark - -class Scanner: - - def __init__(self): - """Initialize the scanner.""" - # It is assumed that Scanner and Reader will have a common descendant. - # Reader do the dirty work of checking for BOM and converting the - # input data to Unicode. It also adds NUL to the end. - # - # Reader supports the following methods - # self.peek(i=0) # peek the next i-th character - # self.prefix(l=1) # peek the next l characters - # self.forward(l=1) # read the next l characters and move the pointer. - - # Had we reached the end of the stream? - self.done = False - - # The number of unclosed '{' and '['. `flow_level == 0` means block - # context. - self.flow_level = 0 - - # List of processed tokens that are not yet emitted. - self.tokens = [] - - # Add the STREAM-START token. - self.fetch_stream_start() - - # Number of tokens that were emitted through the `get_token` method. - self.tokens_taken = 0 - - # The current indentation level. - self.indent = -1 - - # Past indentation levels. - self.indents = [] - - # Variables related to simple keys treatment. - - # A simple key is a key that is not denoted by the '?' indicator. - # Example of simple keys: - # --- - # block simple key: value - # ? not a simple key: - # : { flow simple key: value } - # We emit the KEY token before all keys, so when we find a potential - # simple key, we try to locate the corresponding ':' indicator. - # Simple keys should be limited to a single line and 1024 characters. - - # Can a simple key start at the current position? A simple key may - # start: - # - at the beginning of the line, not counting indentation spaces - # (in block context), - # - after '{', '[', ',' (in the flow context), - # - after '?', ':', '-' (in the block context). - # In the block context, this flag also signifies if a block collection - # may start at the current position. - self.allow_simple_key = True - - # Keep track of possible simple keys. This is a dictionary. The key - # is `flow_level`; there can be no more that one possible simple key - # for each level. The value is a SimpleKey record: - # (token_number, required, index, line, column, mark) - # A simple key may start with ALIAS, ANCHOR, TAG, SCALAR(flow), - # '[', or '{' tokens. - self.possible_simple_keys = {} - - # Public methods. - - def check_token(self, *choices): - # Check if the next token is one of the given types. - while self.need_more_tokens(): - self.fetch_more_tokens() - if self.tokens: - if not choices: - return True - for choice in choices: - if isinstance(self.tokens[0], choice): - return True - return False - - def peek_token(self): - # Return the next token, but do not delete if from the queue. - # Return None if no more tokens. - while self.need_more_tokens(): - self.fetch_more_tokens() - if self.tokens: - return self.tokens[0] - else: - return None - - def get_token(self): - # Return the next token. - while self.need_more_tokens(): - self.fetch_more_tokens() - if self.tokens: - self.tokens_taken += 1 - return self.tokens.pop(0) - - # Private methods. - - def need_more_tokens(self): - if self.done: - return False - if not self.tokens: - return True - # The current token may be a potential simple key, so we - # need to look further. - self.stale_possible_simple_keys() - if self.next_possible_simple_key() == self.tokens_taken: - return True - - def fetch_more_tokens(self): - - # Eat whitespaces and comments until we reach the next token. - self.scan_to_next_token() - - # Remove obsolete possible simple keys. - self.stale_possible_simple_keys() - - # Compare the current indentation and column. It may add some tokens - # and decrease the current indentation level. - self.unwind_indent(self.column) - - # Peek the next character. - ch = self.peek() - - # Is it the end of stream? - if ch == '\0': - return self.fetch_stream_end() - - # Is it a directive? - if ch == '%' and self.check_directive(): - return self.fetch_directive() - - # Is it the document start? - if ch == '-' and self.check_document_start(): - return self.fetch_document_start() - - # Is it the document end? - if ch == '.' and self.check_document_end(): - return self.fetch_document_end() - - # TODO: support for BOM within a stream. - #if ch == '\uFEFF': - # return self.fetch_bom() <-- issue BOMToken - - # Note: the order of the following checks is NOT significant. - - # Is it the flow sequence start indicator? - if ch == '[': - return self.fetch_flow_sequence_start() - - # Is it the flow mapping start indicator? - if ch == '{': - return self.fetch_flow_mapping_start() - - # Is it the flow sequence end indicator? - if ch == ']': - return self.fetch_flow_sequence_end() - - # Is it the flow mapping end indicator? - if ch == '}': - return self.fetch_flow_mapping_end() - - # Is it the flow entry indicator? - if ch == ',': - return self.fetch_flow_entry() - - # Is it the block entry indicator? - if ch == '-' and self.check_block_entry(): - return self.fetch_block_entry() - - # Is it the key indicator? - if ch == '?' and self.check_key(): - return self.fetch_key() - - # Is it the value indicator? - if ch == ':' and self.check_value(): - return self.fetch_value() - - # Is it an alias? - if ch == '*': - return self.fetch_alias() - - # Is it an anchor? - if ch == '&': - return self.fetch_anchor() - - # Is it a tag? - if ch == '!': - return self.fetch_tag() - - # Is it a literal scalar? - if ch == '|' and not self.flow_level: - return self.fetch_literal() - - # Is it a folded scalar? - if ch == '>' and not self.flow_level: - return self.fetch_folded() - - # Is it a single quoted scalar? - if ch == '\'': - return self.fetch_single() - - # Is it a double quoted scalar? - if ch == '\"': - return self.fetch_double() - - # It must be a plain scalar then. - if self.check_plain(): - return self.fetch_plain() - - # No? It's an error. Let's produce a nice error message. - raise ScannerError("while scanning for the next token", None, - "found character %r that cannot start any token" % ch, - self.get_mark()) - - # Simple keys treatment. - - def next_possible_simple_key(self): - # Return the number of the nearest possible simple key. Actually we - # don't need to loop through the whole dictionary. We may replace it - # with the following code: - # if not self.possible_simple_keys: - # return None - # return self.possible_simple_keys[ - # min(self.possible_simple_keys.keys())].token_number - min_token_number = None - for level in self.possible_simple_keys: - key = self.possible_simple_keys[level] - if min_token_number is None or key.token_number < min_token_number: - min_token_number = key.token_number - return min_token_number - - def stale_possible_simple_keys(self): - # Remove entries that are no longer possible simple keys. According to - # the YAML specification, simple keys - # - should be limited to a single line, - # - should be no longer than 1024 characters. - # Disabling this procedure will allow simple keys of any length and - # height (may cause problems if indentation is broken though). - for level in list(self.possible_simple_keys): - key = self.possible_simple_keys[level] - if key.line != self.line \ - or self.index-key.index > 1024: - if key.required: - raise ScannerError("while scanning a simple key", key.mark, - "could not find expected ':'", self.get_mark()) - del self.possible_simple_keys[level] - - def save_possible_simple_key(self): - # The next token may start a simple key. We check if it's possible - # and save its position. This function is called for - # ALIAS, ANCHOR, TAG, SCALAR(flow), '[', and '{'. - - # Check if a simple key is required at the current position. - required = not self.flow_level and self.indent == self.column - - # The next token might be a simple key. Let's save it's number and - # position. - if self.allow_simple_key: - self.remove_possible_simple_key() - token_number = self.tokens_taken+len(self.tokens) - key = SimpleKey(token_number, required, - self.index, self.line, self.column, self.get_mark()) - self.possible_simple_keys[self.flow_level] = key - - def remove_possible_simple_key(self): - # Remove the saved possible key position at the current flow level. - if self.flow_level in self.possible_simple_keys: - key = self.possible_simple_keys[self.flow_level] - - if key.required: - raise ScannerError("while scanning a simple key", key.mark, - "could not find expected ':'", self.get_mark()) - - del self.possible_simple_keys[self.flow_level] - - # Indentation functions. - - def unwind_indent(self, column): - - ## In flow context, tokens should respect indentation. - ## Actually the condition should be `self.indent >= column` according to - ## the spec. But this condition will prohibit intuitively correct - ## constructions such as - ## key : { - ## } - #if self.flow_level and self.indent > column: - # raise ScannerError(None, None, - # "invalid indentation or unclosed '[' or '{'", - # self.get_mark()) - - # In the flow context, indentation is ignored. We make the scanner less - # restrictive then specification requires. - if self.flow_level: - return - - # In block context, we may need to issue the BLOCK-END tokens. - while self.indent > column: - mark = self.get_mark() - self.indent = self.indents.pop() - self.tokens.append(BlockEndToken(mark, mark)) - - def add_indent(self, column): - # Check if we need to increase indentation. - if self.indent < column: - self.indents.append(self.indent) - self.indent = column - return True - return False - - # Fetchers. - - def fetch_stream_start(self): - # We always add STREAM-START as the first token and STREAM-END as the - # last token. - - # Read the token. - mark = self.get_mark() - - # Add STREAM-START. - self.tokens.append(StreamStartToken(mark, mark, - encoding=self.encoding)) - - - def fetch_stream_end(self): - - # Set the current indentation to -1. - self.unwind_indent(-1) - - # Reset simple keys. - self.remove_possible_simple_key() - self.allow_simple_key = False - self.possible_simple_keys = {} - - # Read the token. - mark = self.get_mark() - - # Add STREAM-END. - self.tokens.append(StreamEndToken(mark, mark)) - - # The steam is finished. - self.done = True - - def fetch_directive(self): - - # Set the current indentation to -1. - self.unwind_indent(-1) - - # Reset simple keys. - self.remove_possible_simple_key() - self.allow_simple_key = False - - # Scan and add DIRECTIVE. - self.tokens.append(self.scan_directive()) - - def fetch_document_start(self): - self.fetch_document_indicator(DocumentStartToken) - - def fetch_document_end(self): - self.fetch_document_indicator(DocumentEndToken) - - def fetch_document_indicator(self, TokenClass): - - # Set the current indentation to -1. - self.unwind_indent(-1) - - # Reset simple keys. Note that there could not be a block collection - # after '---'. - self.remove_possible_simple_key() - self.allow_simple_key = False - - # Add DOCUMENT-START or DOCUMENT-END. - start_mark = self.get_mark() - self.forward(3) - end_mark = self.get_mark() - self.tokens.append(TokenClass(start_mark, end_mark)) - - def fetch_flow_sequence_start(self): - self.fetch_flow_collection_start(FlowSequenceStartToken) - - def fetch_flow_mapping_start(self): - self.fetch_flow_collection_start(FlowMappingStartToken) - - def fetch_flow_collection_start(self, TokenClass): - - # '[' and '{' may start a simple key. - self.save_possible_simple_key() - - # Increase the flow level. - self.flow_level += 1 - - # Simple keys are allowed after '[' and '{'. - self.allow_simple_key = True - - # Add FLOW-SEQUENCE-START or FLOW-MAPPING-START. - start_mark = self.get_mark() - self.forward() - end_mark = self.get_mark() - self.tokens.append(TokenClass(start_mark, end_mark)) - - def fetch_flow_sequence_end(self): - self.fetch_flow_collection_end(FlowSequenceEndToken) - - def fetch_flow_mapping_end(self): - self.fetch_flow_collection_end(FlowMappingEndToken) - - def fetch_flow_collection_end(self, TokenClass): - - # Reset possible simple key on the current level. - self.remove_possible_simple_key() - - # Decrease the flow level. - self.flow_level -= 1 - - # No simple keys after ']' or '}'. - self.allow_simple_key = False - - # Add FLOW-SEQUENCE-END or FLOW-MAPPING-END. - start_mark = self.get_mark() - self.forward() - end_mark = self.get_mark() - self.tokens.append(TokenClass(start_mark, end_mark)) - - def fetch_flow_entry(self): - - # Simple keys are allowed after ','. - self.allow_simple_key = True - - # Reset possible simple key on the current level. - self.remove_possible_simple_key() - - # Add FLOW-ENTRY. - start_mark = self.get_mark() - self.forward() - end_mark = self.get_mark() - self.tokens.append(FlowEntryToken(start_mark, end_mark)) - - def fetch_block_entry(self): - - # Block context needs additional checks. - if not self.flow_level: - - # Are we allowed to start a new entry? - if not self.allow_simple_key: - raise ScannerError(None, None, - "sequence entries are not allowed here", - self.get_mark()) - - # We may need to add BLOCK-SEQUENCE-START. - if self.add_indent(self.column): - mark = self.get_mark() - self.tokens.append(BlockSequenceStartToken(mark, mark)) - - # It's an error for the block entry to occur in the flow context, - # but we let the parser detect this. - else: - pass - - # Simple keys are allowed after '-'. - self.allow_simple_key = True - - # Reset possible simple key on the current level. - self.remove_possible_simple_key() - - # Add BLOCK-ENTRY. - start_mark = self.get_mark() - self.forward() - end_mark = self.get_mark() - self.tokens.append(BlockEntryToken(start_mark, end_mark)) - - def fetch_key(self): - - # Block context needs additional checks. - if not self.flow_level: - - # Are we allowed to start a key (not necessary a simple)? - if not self.allow_simple_key: - raise ScannerError(None, None, - "mapping keys are not allowed here", - self.get_mark()) - - # We may need to add BLOCK-MAPPING-START. - if self.add_indent(self.column): - mark = self.get_mark() - self.tokens.append(BlockMappingStartToken(mark, mark)) - - # Simple keys are allowed after '?' in the block context. - self.allow_simple_key = not self.flow_level - - # Reset possible simple key on the current level. - self.remove_possible_simple_key() - - # Add KEY. - start_mark = self.get_mark() - self.forward() - end_mark = self.get_mark() - self.tokens.append(KeyToken(start_mark, end_mark)) - - def fetch_value(self): - - # Do we determine a simple key? - if self.flow_level in self.possible_simple_keys: - - # Add KEY. - key = self.possible_simple_keys[self.flow_level] - del self.possible_simple_keys[self.flow_level] - self.tokens.insert(key.token_number-self.tokens_taken, - KeyToken(key.mark, key.mark)) - - # If this key starts a new block mapping, we need to add - # BLOCK-MAPPING-START. - if not self.flow_level: - if self.add_indent(key.column): - self.tokens.insert(key.token_number-self.tokens_taken, - BlockMappingStartToken(key.mark, key.mark)) - - # There cannot be two simple keys one after another. - self.allow_simple_key = False - - # It must be a part of a complex key. - else: - - # Block context needs additional checks. - # (Do we really need them? They will be caught by the parser - # anyway.) - if not self.flow_level: - - # We are allowed to start a complex value if and only if - # we can start a simple key. - if not self.allow_simple_key: - raise ScannerError(None, None, - "mapping values are not allowed here", - self.get_mark()) - - # If this value starts a new block mapping, we need to add - # BLOCK-MAPPING-START. It will be detected as an error later by - # the parser. - if not self.flow_level: - if self.add_indent(self.column): - mark = self.get_mark() - self.tokens.append(BlockMappingStartToken(mark, mark)) - - # Simple keys are allowed after ':' in the block context. - self.allow_simple_key = not self.flow_level - - # Reset possible simple key on the current level. - self.remove_possible_simple_key() - - # Add VALUE. - start_mark = self.get_mark() - self.forward() - end_mark = self.get_mark() - self.tokens.append(ValueToken(start_mark, end_mark)) - - def fetch_alias(self): - - # ALIAS could be a simple key. - self.save_possible_simple_key() - - # No simple keys after ALIAS. - self.allow_simple_key = False - - # Scan and add ALIAS. - self.tokens.append(self.scan_anchor(AliasToken)) - - def fetch_anchor(self): - - # ANCHOR could start a simple key. - self.save_possible_simple_key() - - # No simple keys after ANCHOR. - self.allow_simple_key = False - - # Scan and add ANCHOR. - self.tokens.append(self.scan_anchor(AnchorToken)) - - def fetch_tag(self): - - # TAG could start a simple key. - self.save_possible_simple_key() - - # No simple keys after TAG. - self.allow_simple_key = False - - # Scan and add TAG. - self.tokens.append(self.scan_tag()) - - def fetch_literal(self): - self.fetch_block_scalar(style='|') - - def fetch_folded(self): - self.fetch_block_scalar(style='>') - - def fetch_block_scalar(self, style): - - # A simple key may follow a block scalar. - self.allow_simple_key = True - - # Reset possible simple key on the current level. - self.remove_possible_simple_key() - - # Scan and add SCALAR. - self.tokens.append(self.scan_block_scalar(style)) - - def fetch_single(self): - self.fetch_flow_scalar(style='\'') - - def fetch_double(self): - self.fetch_flow_scalar(style='"') - - def fetch_flow_scalar(self, style): - - # A flow scalar could be a simple key. - self.save_possible_simple_key() - - # No simple keys after flow scalars. - self.allow_simple_key = False - - # Scan and add SCALAR. - self.tokens.append(self.scan_flow_scalar(style)) - - def fetch_plain(self): - - # A plain scalar could be a simple key. - self.save_possible_simple_key() - - # No simple keys after plain scalars. But note that `scan_plain` will - # change this flag if the scan is finished at the beginning of the - # line. - self.allow_simple_key = False - - # Scan and add SCALAR. May change `allow_simple_key`. - self.tokens.append(self.scan_plain()) - - # Checkers. - - def check_directive(self): - - # DIRECTIVE: ^ '%' ... - # The '%' indicator is already checked. - if self.column == 0: - return True - - def check_document_start(self): - - # DOCUMENT-START: ^ '---' (' '|'\n') - if self.column == 0: - if self.prefix(3) == '---' \ - and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': - return True - - def check_document_end(self): - - # DOCUMENT-END: ^ '...' (' '|'\n') - if self.column == 0: - if self.prefix(3) == '...' \ - and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': - return True - - def check_block_entry(self): - - # BLOCK-ENTRY: '-' (' '|'\n') - return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' - - def check_key(self): - - # KEY(flow context): '?' - if self.flow_level: - return True - - # KEY(block context): '?' (' '|'\n') - else: - return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' - - def check_value(self): - - # VALUE(flow context): ':' - if self.flow_level: - return True - - # VALUE(block context): ':' (' '|'\n') - else: - return self.peek(1) in '\0 \t\r\n\x85\u2028\u2029' - - def check_plain(self): - - # A plain scalar may start with any non-space character except: - # '-', '?', ':', ',', '[', ']', '{', '}', - # '#', '&', '*', '!', '|', '>', '\'', '\"', - # '%', '@', '`'. - # - # It may also start with - # '-', '?', ':' - # if it is followed by a non-space character. - # - # Note that we limit the last rule to the block context (except the - # '-' character) because we want the flow context to be space - # independent. - ch = self.peek() - return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \ - or (self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029' - and (ch == '-' or (not self.flow_level and ch in '?:'))) - - # Scanners. - - def scan_to_next_token(self): - # We ignore spaces, line breaks and comments. - # If we find a line break in the block context, we set the flag - # `allow_simple_key` on. - # The byte order mark is stripped if it's the first character in the - # stream. We do not yet support BOM inside the stream as the - # specification requires. Any such mark will be considered as a part - # of the document. - # - # TODO: We need to make tab handling rules more sane. A good rule is - # Tabs cannot precede tokens - # BLOCK-SEQUENCE-START, BLOCK-MAPPING-START, BLOCK-END, - # KEY(block), VALUE(block), BLOCK-ENTRY - # So the checking code is - # if : - # self.allow_simple_keys = False - # We also need to add the check for `allow_simple_keys == True` to - # `unwind_indent` before issuing BLOCK-END. - # Scanners for block, flow, and plain scalars need to be modified. - - if self.index == 0 and self.peek() == '\uFEFF': - self.forward() - found = False - while not found: - while self.peek() == ' ': - self.forward() - if self.peek() == '#': - while self.peek() not in '\0\r\n\x85\u2028\u2029': - self.forward() - if self.scan_line_break(): - if not self.flow_level: - self.allow_simple_key = True - else: - found = True - - def scan_directive(self): - # See the specification for details. - start_mark = self.get_mark() - self.forward() - name = self.scan_directive_name(start_mark) - value = None - if name == 'YAML': - value = self.scan_yaml_directive_value(start_mark) - end_mark = self.get_mark() - elif name == 'TAG': - value = self.scan_tag_directive_value(start_mark) - end_mark = self.get_mark() - else: - end_mark = self.get_mark() - while self.peek() not in '\0\r\n\x85\u2028\u2029': - self.forward() - self.scan_directive_ignored_line(start_mark) - return DirectiveToken(name, value, start_mark, end_mark) - - def scan_directive_name(self, start_mark): - # See the specification for details. - length = 0 - ch = self.peek(length) - while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-_': - length += 1 - ch = self.peek(length) - if not length: - raise ScannerError("while scanning a directive", start_mark, - "expected alphabetic or numeric character, but found %r" - % ch, self.get_mark()) - value = self.prefix(length) - self.forward(length) - ch = self.peek() - if ch not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a directive", start_mark, - "expected alphabetic or numeric character, but found %r" - % ch, self.get_mark()) - return value - - def scan_yaml_directive_value(self, start_mark): - # See the specification for details. - while self.peek() == ' ': - self.forward() - major = self.scan_yaml_directive_number(start_mark) - if self.peek() != '.': - raise ScannerError("while scanning a directive", start_mark, - "expected a digit or '.', but found %r" % self.peek(), - self.get_mark()) - self.forward() - minor = self.scan_yaml_directive_number(start_mark) - if self.peek() not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a directive", start_mark, - "expected a digit or ' ', but found %r" % self.peek(), - self.get_mark()) - return (major, minor) - - def scan_yaml_directive_number(self, start_mark): - # See the specification for details. - ch = self.peek() - if not ('0' <= ch <= '9'): - raise ScannerError("while scanning a directive", start_mark, - "expected a digit, but found %r" % ch, self.get_mark()) - length = 0 - while '0' <= self.peek(length) <= '9': - length += 1 - value = int(self.prefix(length)) - self.forward(length) - return value - - def scan_tag_directive_value(self, start_mark): - # See the specification for details. - while self.peek() == ' ': - self.forward() - handle = self.scan_tag_directive_handle(start_mark) - while self.peek() == ' ': - self.forward() - prefix = self.scan_tag_directive_prefix(start_mark) - return (handle, prefix) - - def scan_tag_directive_handle(self, start_mark): - # See the specification for details. - value = self.scan_tag_handle('directive', start_mark) - ch = self.peek() - if ch != ' ': - raise ScannerError("while scanning a directive", start_mark, - "expected ' ', but found %r" % ch, self.get_mark()) - return value - - def scan_tag_directive_prefix(self, start_mark): - # See the specification for details. - value = self.scan_tag_uri('directive', start_mark) - ch = self.peek() - if ch not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a directive", start_mark, - "expected ' ', but found %r" % ch, self.get_mark()) - return value - - def scan_directive_ignored_line(self, start_mark): - # See the specification for details. - while self.peek() == ' ': - self.forward() - if self.peek() == '#': - while self.peek() not in '\0\r\n\x85\u2028\u2029': - self.forward() - ch = self.peek() - if ch not in '\0\r\n\x85\u2028\u2029': - raise ScannerError("while scanning a directive", start_mark, - "expected a comment or a line break, but found %r" - % ch, self.get_mark()) - self.scan_line_break() - - def scan_anchor(self, TokenClass): - # The specification does not restrict characters for anchors and - # aliases. This may lead to problems, for instance, the document: - # [ *alias, value ] - # can be interpreted in two ways, as - # [ "value" ] - # and - # [ *alias , "value" ] - # Therefore we restrict aliases to numbers and ASCII letters. - start_mark = self.get_mark() - indicator = self.peek() - if indicator == '*': - name = 'alias' - else: - name = 'anchor' - self.forward() - length = 0 - ch = self.peek(length) - while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-_': - length += 1 - ch = self.peek(length) - if not length: - raise ScannerError("while scanning an %s" % name, start_mark, - "expected alphabetic or numeric character, but found %r" - % ch, self.get_mark()) - value = self.prefix(length) - self.forward(length) - ch = self.peek() - if ch not in '\0 \t\r\n\x85\u2028\u2029?:,]}%@`': - raise ScannerError("while scanning an %s" % name, start_mark, - "expected alphabetic or numeric character, but found %r" - % ch, self.get_mark()) - end_mark = self.get_mark() - return TokenClass(value, start_mark, end_mark) - - def scan_tag(self): - # See the specification for details. - start_mark = self.get_mark() - ch = self.peek(1) - if ch == '<': - handle = None - self.forward(2) - suffix = self.scan_tag_uri('tag', start_mark) - if self.peek() != '>': - raise ScannerError("while parsing a tag", start_mark, - "expected '>', but found %r" % self.peek(), - self.get_mark()) - self.forward() - elif ch in '\0 \t\r\n\x85\u2028\u2029': - handle = None - suffix = '!' - self.forward() - else: - length = 1 - use_handle = False - while ch not in '\0 \r\n\x85\u2028\u2029': - if ch == '!': - use_handle = True - break - length += 1 - ch = self.peek(length) - handle = '!' - if use_handle: - handle = self.scan_tag_handle('tag', start_mark) - else: - handle = '!' - self.forward() - suffix = self.scan_tag_uri('tag', start_mark) - ch = self.peek() - if ch not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a tag", start_mark, - "expected ' ', but found %r" % ch, self.get_mark()) - value = (handle, suffix) - end_mark = self.get_mark() - return TagToken(value, start_mark, end_mark) - - def scan_block_scalar(self, style): - # See the specification for details. - - if style == '>': - folded = True - else: - folded = False - - chunks = [] - start_mark = self.get_mark() - - # Scan the header. - self.forward() - chomping, increment = self.scan_block_scalar_indicators(start_mark) - self.scan_block_scalar_ignored_line(start_mark) - - # Determine the indentation level and go to the first non-empty line. - min_indent = self.indent+1 - if min_indent < 1: - min_indent = 1 - if increment is None: - breaks, max_indent, end_mark = self.scan_block_scalar_indentation() - indent = max(min_indent, max_indent) - else: - indent = min_indent+increment-1 - breaks, end_mark = self.scan_block_scalar_breaks(indent) - line_break = '' - - # Scan the inner part of the block scalar. - while self.column == indent and self.peek() != '\0': - chunks.extend(breaks) - leading_non_space = self.peek() not in ' \t' - length = 0 - while self.peek(length) not in '\0\r\n\x85\u2028\u2029': - length += 1 - chunks.append(self.prefix(length)) - self.forward(length) - line_break = self.scan_line_break() - breaks, end_mark = self.scan_block_scalar_breaks(indent) - if self.column == indent and self.peek() != '\0': - - # Unfortunately, folding rules are ambiguous. - # - # This is the folding according to the specification: - - if folded and line_break == '\n' \ - and leading_non_space and self.peek() not in ' \t': - if not breaks: - chunks.append(' ') - else: - chunks.append(line_break) - - # This is Clark Evans's interpretation (also in the spec - # examples): - # - #if folded and line_break == '\n': - # if not breaks: - # if self.peek() not in ' \t': - # chunks.append(' ') - # else: - # chunks.append(line_break) - #else: - # chunks.append(line_break) - else: - break - - # Chomp the tail. - if chomping is not False: - chunks.append(line_break) - if chomping is True: - chunks.extend(breaks) - - # We are done. - return ScalarToken(''.join(chunks), False, start_mark, end_mark, - style) - - def scan_block_scalar_indicators(self, start_mark): - # See the specification for details. - chomping = None - increment = None - ch = self.peek() - if ch in '+-': - if ch == '+': - chomping = True - else: - chomping = False - self.forward() - ch = self.peek() - if ch in '0123456789': - increment = int(ch) - if increment == 0: - raise ScannerError("while scanning a block scalar", start_mark, - "expected indentation indicator in the range 1-9, but found 0", - self.get_mark()) - self.forward() - elif ch in '0123456789': - increment = int(ch) - if increment == 0: - raise ScannerError("while scanning a block scalar", start_mark, - "expected indentation indicator in the range 1-9, but found 0", - self.get_mark()) - self.forward() - ch = self.peek() - if ch in '+-': - if ch == '+': - chomping = True - else: - chomping = False - self.forward() - ch = self.peek() - if ch not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a block scalar", start_mark, - "expected chomping or indentation indicators, but found %r" - % ch, self.get_mark()) - return chomping, increment - - def scan_block_scalar_ignored_line(self, start_mark): - # See the specification for details. - while self.peek() == ' ': - self.forward() - if self.peek() == '#': - while self.peek() not in '\0\r\n\x85\u2028\u2029': - self.forward() - ch = self.peek() - if ch not in '\0\r\n\x85\u2028\u2029': - raise ScannerError("while scanning a block scalar", start_mark, - "expected a comment or a line break, but found %r" % ch, - self.get_mark()) - self.scan_line_break() - - def scan_block_scalar_indentation(self): - # See the specification for details. - chunks = [] - max_indent = 0 - end_mark = self.get_mark() - while self.peek() in ' \r\n\x85\u2028\u2029': - if self.peek() != ' ': - chunks.append(self.scan_line_break()) - end_mark = self.get_mark() - else: - self.forward() - if self.column > max_indent: - max_indent = self.column - return chunks, max_indent, end_mark - - def scan_block_scalar_breaks(self, indent): - # See the specification for details. - chunks = [] - end_mark = self.get_mark() - while self.column < indent and self.peek() == ' ': - self.forward() - while self.peek() in '\r\n\x85\u2028\u2029': - chunks.append(self.scan_line_break()) - end_mark = self.get_mark() - while self.column < indent and self.peek() == ' ': - self.forward() - return chunks, end_mark - - def scan_flow_scalar(self, style): - # See the specification for details. - # Note that we loose indentation rules for quoted scalars. Quoted - # scalars don't need to adhere indentation because " and ' clearly - # mark the beginning and the end of them. Therefore we are less - # restrictive then the specification requires. We only need to check - # that document separators are not included in scalars. - if style == '"': - double = True - else: - double = False - chunks = [] - start_mark = self.get_mark() - quote = self.peek() - self.forward() - chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) - while self.peek() != quote: - chunks.extend(self.scan_flow_scalar_spaces(double, start_mark)) - chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) - self.forward() - end_mark = self.get_mark() - return ScalarToken(''.join(chunks), False, start_mark, end_mark, - style) - - ESCAPE_REPLACEMENTS = { - '0': '\0', - 'a': '\x07', - 'b': '\x08', - 't': '\x09', - '\t': '\x09', - 'n': '\x0A', - 'v': '\x0B', - 'f': '\x0C', - 'r': '\x0D', - 'e': '\x1B', - ' ': '\x20', - '\"': '\"', - '\\': '\\', - '/': '/', - 'N': '\x85', - '_': '\xA0', - 'L': '\u2028', - 'P': '\u2029', - } - - ESCAPE_CODES = { - 'x': 2, - 'u': 4, - 'U': 8, - } - - def scan_flow_scalar_non_spaces(self, double, start_mark): - # See the specification for details. - chunks = [] - while True: - length = 0 - while self.peek(length) not in '\'\"\\\0 \t\r\n\x85\u2028\u2029': - length += 1 - if length: - chunks.append(self.prefix(length)) - self.forward(length) - ch = self.peek() - if not double and ch == '\'' and self.peek(1) == '\'': - chunks.append('\'') - self.forward(2) - elif (double and ch == '\'') or (not double and ch in '\"\\'): - chunks.append(ch) - self.forward() - elif double and ch == '\\': - self.forward() - ch = self.peek() - if ch in self.ESCAPE_REPLACEMENTS: - chunks.append(self.ESCAPE_REPLACEMENTS[ch]) - self.forward() - elif ch in self.ESCAPE_CODES: - length = self.ESCAPE_CODES[ch] - self.forward() - for k in range(length): - if self.peek(k) not in '0123456789ABCDEFabcdef': - raise ScannerError("while scanning a double-quoted scalar", start_mark, - "expected escape sequence of %d hexdecimal numbers, but found %r" % - (length, self.peek(k)), self.get_mark()) - code = int(self.prefix(length), 16) - chunks.append(chr(code)) - self.forward(length) - elif ch in '\r\n\x85\u2028\u2029': - self.scan_line_break() - chunks.extend(self.scan_flow_scalar_breaks(double, start_mark)) - else: - raise ScannerError("while scanning a double-quoted scalar", start_mark, - "found unknown escape character %r" % ch, self.get_mark()) - else: - return chunks - - def scan_flow_scalar_spaces(self, double, start_mark): - # See the specification for details. - chunks = [] - length = 0 - while self.peek(length) in ' \t': - length += 1 - whitespaces = self.prefix(length) - self.forward(length) - ch = self.peek() - if ch == '\0': - raise ScannerError("while scanning a quoted scalar", start_mark, - "found unexpected end of stream", self.get_mark()) - elif ch in '\r\n\x85\u2028\u2029': - line_break = self.scan_line_break() - breaks = self.scan_flow_scalar_breaks(double, start_mark) - if line_break != '\n': - chunks.append(line_break) - elif not breaks: - chunks.append(' ') - chunks.extend(breaks) - else: - chunks.append(whitespaces) - return chunks - - def scan_flow_scalar_breaks(self, double, start_mark): - # See the specification for details. - chunks = [] - while True: - # Instead of checking indentation, we check for document - # separators. - prefix = self.prefix(3) - if (prefix == '---' or prefix == '...') \ - and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': - raise ScannerError("while scanning a quoted scalar", start_mark, - "found unexpected document separator", self.get_mark()) - while self.peek() in ' \t': - self.forward() - if self.peek() in '\r\n\x85\u2028\u2029': - chunks.append(self.scan_line_break()) - else: - return chunks - - def scan_plain(self): - # See the specification for details. - # We add an additional restriction for the flow context: - # plain scalars in the flow context cannot contain ',' or '?'. - # We also keep track of the `allow_simple_key` flag here. - # Indentation rules are loosed for the flow context. - chunks = [] - start_mark = self.get_mark() - end_mark = start_mark - indent = self.indent+1 - # We allow zero indentation for scalars, but then we need to check for - # document separators at the beginning of the line. - #if indent == 0: - # indent = 1 - spaces = [] - while True: - length = 0 - if self.peek() == '#': - break - while True: - ch = self.peek(length) - if ch in '\0 \t\r\n\x85\u2028\u2029' \ - or (ch == ':' and - self.peek(length+1) in '\0 \t\r\n\x85\u2028\u2029' - + (u',[]{}' if self.flow_level else u''))\ - or (self.flow_level and ch in ',?[]{}'): - break - length += 1 - if length == 0: - break - self.allow_simple_key = False - chunks.extend(spaces) - chunks.append(self.prefix(length)) - self.forward(length) - end_mark = self.get_mark() - spaces = self.scan_plain_spaces(indent, start_mark) - if not spaces or self.peek() == '#' \ - or (not self.flow_level and self.column < indent): - break - return ScalarToken(''.join(chunks), True, start_mark, end_mark) - - def scan_plain_spaces(self, indent, start_mark): - # See the specification for details. - # The specification is really confusing about tabs in plain scalars. - # We just forbid them completely. Do not use tabs in YAML! - chunks = [] - length = 0 - while self.peek(length) in ' ': - length += 1 - whitespaces = self.prefix(length) - self.forward(length) - ch = self.peek() - if ch in '\r\n\x85\u2028\u2029': - line_break = self.scan_line_break() - self.allow_simple_key = True - prefix = self.prefix(3) - if (prefix == '---' or prefix == '...') \ - and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': - return - breaks = [] - while self.peek() in ' \r\n\x85\u2028\u2029': - if self.peek() == ' ': - self.forward() - else: - breaks.append(self.scan_line_break()) - prefix = self.prefix(3) - if (prefix == '---' or prefix == '...') \ - and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': - return - if line_break != '\n': - chunks.append(line_break) - elif not breaks: - chunks.append(' ') - chunks.extend(breaks) - elif whitespaces: - chunks.append(whitespaces) - return chunks - - def scan_tag_handle(self, name, start_mark): - # See the specification for details. - # For some strange reasons, the specification does not allow '_' in - # tag handles. I have allowed it anyway. - ch = self.peek() - if ch != '!': - raise ScannerError("while scanning a %s" % name, start_mark, - "expected '!', but found %r" % ch, self.get_mark()) - length = 1 - ch = self.peek(length) - if ch != ' ': - while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-_': - length += 1 - ch = self.peek(length) - if ch != '!': - self.forward(length) - raise ScannerError("while scanning a %s" % name, start_mark, - "expected '!', but found %r" % ch, self.get_mark()) - length += 1 - value = self.prefix(length) - self.forward(length) - return value - - def scan_tag_uri(self, name, start_mark): - # See the specification for details. - # Note: we do not check if URI is well-formed. - chunks = [] - length = 0 - ch = self.peek(length) - while '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-;/?:@&=+$,_.!~*\'()[]%': - if ch == '%': - chunks.append(self.prefix(length)) - self.forward(length) - length = 0 - chunks.append(self.scan_uri_escapes(name, start_mark)) - else: - length += 1 - ch = self.peek(length) - if length: - chunks.append(self.prefix(length)) - self.forward(length) - length = 0 - if not chunks: - raise ScannerError("while parsing a %s" % name, start_mark, - "expected URI, but found %r" % ch, self.get_mark()) - return ''.join(chunks) - - def scan_uri_escapes(self, name, start_mark): - # See the specification for details. - codes = [] - mark = self.get_mark() - while self.peek() == '%': - self.forward() - for k in range(2): - if self.peek(k) not in '0123456789ABCDEFabcdef': - raise ScannerError("while scanning a %s" % name, start_mark, - "expected URI escape sequence of 2 hexdecimal numbers, but found %r" - % self.peek(k), self.get_mark()) - codes.append(int(self.prefix(2), 16)) - self.forward(2) - try: - value = bytes(codes).decode('utf-8') - except UnicodeDecodeError as exc: - raise ScannerError("while scanning a %s" % name, start_mark, str(exc), mark) - return value - - def scan_line_break(self): - # Transforms: - # '\r\n' : '\n' - # '\r' : '\n' - # '\n' : '\n' - # '\x85' : '\n' - # '\u2028' : '\u2028' - # '\u2029 : '\u2029' - # default : '' - ch = self.peek() - if ch in '\r\n\x85': - if self.prefix(2) == '\r\n': - self.forward(2) - else: - self.forward() - return '\n' - elif ch in '\u2028\u2029': - self.forward() - return ch - return '' diff --git a/coredns/venv/yaml/serializer.py b/coredns/venv/yaml/serializer.py deleted file mode 100644 index fe911e6..0000000 --- a/coredns/venv/yaml/serializer.py +++ /dev/null @@ -1,111 +0,0 @@ - -__all__ = ['Serializer', 'SerializerError'] - -from .error import YAMLError -from .events import * -from .nodes import * - -class SerializerError(YAMLError): - pass - -class Serializer: - - ANCHOR_TEMPLATE = 'id%03d' - - def __init__(self, encoding=None, - explicit_start=None, explicit_end=None, version=None, tags=None): - self.use_encoding = encoding - self.use_explicit_start = explicit_start - self.use_explicit_end = explicit_end - self.use_version = version - self.use_tags = tags - self.serialized_nodes = {} - self.anchors = {} - self.last_anchor_id = 0 - self.closed = None - - def open(self): - if self.closed is None: - self.emit(StreamStartEvent(encoding=self.use_encoding)) - self.closed = False - elif self.closed: - raise SerializerError("serializer is closed") - else: - raise SerializerError("serializer is already opened") - - def close(self): - if self.closed is None: - raise SerializerError("serializer is not opened") - elif not self.closed: - self.emit(StreamEndEvent()) - self.closed = True - - #def __del__(self): - # self.close() - - def serialize(self, node): - if self.closed is None: - raise SerializerError("serializer is not opened") - elif self.closed: - raise SerializerError("serializer is closed") - self.emit(DocumentStartEvent(explicit=self.use_explicit_start, - version=self.use_version, tags=self.use_tags)) - self.anchor_node(node) - self.serialize_node(node, None, None) - self.emit(DocumentEndEvent(explicit=self.use_explicit_end)) - self.serialized_nodes = {} - self.anchors = {} - self.last_anchor_id = 0 - - def anchor_node(self, node): - if node in self.anchors: - if self.anchors[node] is None: - self.anchors[node] = self.generate_anchor(node) - else: - self.anchors[node] = None - if isinstance(node, SequenceNode): - for item in node.value: - self.anchor_node(item) - elif isinstance(node, MappingNode): - for key, value in node.value: - self.anchor_node(key) - self.anchor_node(value) - - def generate_anchor(self, node): - self.last_anchor_id += 1 - return self.ANCHOR_TEMPLATE % self.last_anchor_id - - def serialize_node(self, node, parent, index): - alias = self.anchors[node] - if node in self.serialized_nodes: - self.emit(AliasEvent(alias)) - else: - self.serialized_nodes[node] = True - self.descend_resolver(parent, index) - if isinstance(node, ScalarNode): - detected_tag = self.resolve(ScalarNode, node.value, (True, False)) - default_tag = self.resolve(ScalarNode, node.value, (False, True)) - implicit = (node.tag == detected_tag), (node.tag == default_tag) - self.emit(ScalarEvent(alias, node.tag, implicit, node.value, - style=node.style)) - elif isinstance(node, SequenceNode): - implicit = (node.tag - == self.resolve(SequenceNode, node.value, True)) - self.emit(SequenceStartEvent(alias, node.tag, implicit, - flow_style=node.flow_style)) - index = 0 - for item in node.value: - self.serialize_node(item, node, index) - index += 1 - self.emit(SequenceEndEvent()) - elif isinstance(node, MappingNode): - implicit = (node.tag - == self.resolve(MappingNode, node.value, True)) - self.emit(MappingStartEvent(alias, node.tag, implicit, - flow_style=node.flow_style)) - for key, value in node.value: - self.serialize_node(key, node, None) - self.serialize_node(value, node, key) - self.emit(MappingEndEvent()) - self.ascend_resolver() - diff --git a/coredns/venv/yaml/tokens.py b/coredns/venv/yaml/tokens.py deleted file mode 100644 index 4d0b48a..0000000 --- a/coredns/venv/yaml/tokens.py +++ /dev/null @@ -1,104 +0,0 @@ - -class Token(object): - def __init__(self, start_mark, end_mark): - self.start_mark = start_mark - self.end_mark = end_mark - def __repr__(self): - attributes = [key for key in self.__dict__ - if not key.endswith('_mark')] - attributes.sort() - arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) - for key in attributes]) - return '%s(%s)' % (self.__class__.__name__, arguments) - -#class BOMToken(Token): -# id = '' - -class DirectiveToken(Token): - id = '' - def __init__(self, name, value, start_mark, end_mark): - self.name = name - self.value = value - self.start_mark = start_mark - self.end_mark = end_mark - -class DocumentStartToken(Token): - id = '' - -class DocumentEndToken(Token): - id = '' - -class StreamStartToken(Token): - id = '' - def __init__(self, start_mark=None, end_mark=None, - encoding=None): - self.start_mark = start_mark - self.end_mark = end_mark - self.encoding = encoding - -class StreamEndToken(Token): - id = '' - -class BlockSequenceStartToken(Token): - id = '' - -class BlockMappingStartToken(Token): - id = '' - -class BlockEndToken(Token): - id = '' - -class FlowSequenceStartToken(Token): - id = '[' - -class FlowMappingStartToken(Token): - id = '{' - -class FlowSequenceEndToken(Token): - id = ']' - -class FlowMappingEndToken(Token): - id = '}' - -class KeyToken(Token): - id = '?' - -class ValueToken(Token): - id = ':' - -class BlockEntryToken(Token): - id = '-' - -class FlowEntryToken(Token): - id = ',' - -class AliasToken(Token): - id = '' - def __init__(self, value, start_mark, end_mark): - self.value = value - self.start_mark = start_mark - self.end_mark = end_mark - -class AnchorToken(Token): - id = '' - def __init__(self, value, start_mark, end_mark): - self.value = value - self.start_mark = start_mark - self.end_mark = end_mark - -class TagToken(Token): - id = '' - def __init__(self, value, start_mark, end_mark): - self.value = value - self.start_mark = start_mark - self.end_mark = end_mark - -class ScalarToken(Token): - id = '' - def __init__(self, value, plain, start_mark, end_mark, style=None): - self.value = value - self.plain = plain - self.start_mark = start_mark - self.end_mark = end_mark - self.style = style - diff --git a/kata/.build.manifest b/kata/.build.manifest deleted file mode 100644 index 729b0c1..0000000 --- a/kata/.build.manifest +++ /dev/null @@ -1,531 +0,0 @@ -{ - "layers": [ - { - "branch": "refs/heads/master\nrefs/heads/stable", - "rev": "fcdcea4e5de3e1556c24e6704607862d0ba00a56", - "url": "layer:options" - }, - { - "branch": "refs/heads/stable", - "rev": "0d10732a6e14ea2f940a35ab61425a97c5db6a16", - "url": "layer:basic" - }, - { - "branch": "refs/heads/master\nrefs/heads/stable", - "rev": "527dd64fc4b9a6b0f8d80a3c2c0b865155050275", - "url": "layer:debug" - }, - { - "branch": "refs/heads/master\nrefs/heads/stable", - "rev": "a7d7b6423db37a47611310039e6ed1929c0a2eab", - "url": "layer:status" - }, - { - "branch": "refs/heads/stable", - "rev": "b2fa345285b14fe339084fd35865973ca05eefbf", - "url": "kata" - }, - { - "branch": "refs/heads/master\nrefs/heads/stable", - "rev": "6f927f10b97f45c566481cf57a29d433f17373e1", - "url": "interface:container-runtime" - }, - { - "branch": "refs/heads/master\nrefs/heads/stable", - "rev": "b59ce0c44bc52c789175750ce18b42f76c9a4578", - "url": "interface:untrusted-container-runtime" - } - ], - "signatures": { - ".build.manifest": [ - "build", - "dynamic", - "unchecked" - ], - ".gitignore": [ - "kata", - "static", - "589384c900fb8e573ae6939a9efa0813087ea526761ba661d96aa2526a494eef" - ], - ".travis.yml": [ - "kata", - "static", - "714ed5453bd5a053676efb64370194a7c130f426ec11acba7d1509d558dc979c" - ], - ".travis/profile-update.yaml": [ - "layer:basic", - "static", - "731e20aa59bf61c024d317ad630e478301a9386ccc0afe56e6c1c09db07ac83b" - ], - "CONTRIBUTING.md": [ - "kata", - "static", - "c44755a6800e330bd939b7a27a4bb75adaef3a1ccdc15df62cb5533a3ea6252f" - ], - "LICENSE": [ - "kata", - "static", - "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" - ], - "Makefile": [ - "layer:basic", - "static", - "b7ab3a34e5faf79b96a8632039a0ad0aa87f2a9b5f0ba604e007cafb22190301" - ], - "README.md": [ - "kata", - "static", - "ac3b4f06b6e4a23f80a12f898645c4d4c2daedf961e72a2d851cf9c4b37d538a" - ], - "actions.yaml": [ - "layer:debug", - "dynamic", - "cea290e28bc78458ea4a56dcad39b9a880c67e4ba53b774ac46bd8778618c7b9" - ], - "actions/debug": [ - "layer:debug", - "static", - "db0a42dae4c5045b2c06385bf22209dfe0e2ded55822ef847d84b01d9ff2b046" - ], - "bin/charm-env": [ - "layer:basic", - "static", - "fb6a20fac4102a6a4b6ffe903fcf666998f9a95a3647e6f9af7a1eeb44e58fd5" - ], - "bin/layer_option": [ - "layer:options", - "static", - "e959bf29da4c5edff28b2602c24113c4df9e25cdc9f2aa3b5d46c8577b2a40cc" - ], - "copyright": [ - "layer:status", - "static", - "7c0e36e618a8544faaaa3f8e0533c2f1f4a18bcacbdd8b99b537742e6b587d58" - ], - "copyright.layer-basic": [ - "layer:basic", - "static", - "f6740d66fd60b60f2533d9fcb53907078d1e20920a0219afce7182e2a1c97629" - ], - "copyright.layer-options": [ - "layer:options", - "static", - "f6740d66fd60b60f2533d9fcb53907078d1e20920a0219afce7182e2a1c97629" - ], - "debug-scripts/charm-unitdata": [ - "layer:debug", - "static", - "c952b9d31f3942e4e722cb3e70f5119707b69b8e76cc44e2e906bc6d9aef49b7" - ], - "debug-scripts/filesystem": [ - "layer:debug", - "static", - "d29cc8687f4422d024001c91b1ac756ee6bf8a2a125bc98db1199ba775eb8fd7" - ], - "debug-scripts/juju-logs": [ - "layer:debug", - "static", - "d260b35753a917368cb8c64c1312546a0a40ef49cba84c75bc6369549807c55e" - ], - "debug-scripts/juju-network-get": [ - "layer:debug", - "static", - "6d849a1f8e6569bd0d5ea38299f7937cb8b36a5f505e3532f6c756eabeb8b6c5" - ], - "debug-scripts/network": [ - "layer:debug", - "static", - "714afae5dcb45554ff1f05285501e3b7fcc656c8de51217e263b93dab25a9d2e" - ], - "debug-scripts/packages": [ - "layer:debug", - "static", - "e8177102dc2ca853cb9272c1257cf2cfd5253d2a074e602d07c8bc4ea8e27c75" - ], - "debug-scripts/sysctl": [ - "layer:debug", - "static", - "990035b320e09cc2228e1f2f880e795d51118b2959339eacddff9cbb74349c6a" - ], - "debug-scripts/systemd": [ - "layer:debug", - "static", - "23ddf533198bf5b1ce723acde31ada806aab8539292b514c721d8ec08af74106" - ], - "docs/status.md": [ - "layer:status", - "static", - "975dec9f8c938196e102e954a80226bda293407c4e5ae857c118bf692154702a" - ], - "hooks/config-changed": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/containerd-relation-broken": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/containerd-relation-changed": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/containerd-relation-created": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/containerd-relation-departed": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/containerd-relation-joined": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/hook.template": [ - "layer:basic", - "static", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/install": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/leader-elected": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/leader-settings-changed": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/post-series-upgrade": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/pre-series-upgrade": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/relations/container-runtime/.gitignore": [ - "interface:container-runtime", - "static", - "a2ebfecdb6c1b58267fbe97e6e2ac02c2b963df7673fc1047270f0f0cff16732" - ], - "hooks/relations/container-runtime/LICENSE": [ - "interface:container-runtime", - "static", - "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" - ], - "hooks/relations/container-runtime/README.md": [ - "interface:container-runtime", - "static", - "44273265818229d2c858c3af0e0eee3a7df05aaa9ab20d28c3872190d4b48611" - ], - "hooks/relations/container-runtime/__init__.py": [ - "interface:container-runtime", - "static", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - ], - "hooks/relations/container-runtime/interface.yaml": [ - "interface:container-runtime", - "static", - "e5343dcb11a6817a6050df4ea1c463eeaa0dd4777098566d4e27b056775426c6" - ], - "hooks/relations/container-runtime/provides.py": [ - "interface:container-runtime", - "static", - "4e818da222f507604179a828629787a1250083c847277f6b5b8e028cfbbb6d06" - ], - "hooks/relations/container-runtime/requires.py": [ - "interface:container-runtime", - "static", - "95285168b02f1f70be15c03098833a85e60fa1658ed72a46acd42e8e85ded761" - ], - "hooks/relations/untrusted-container-runtime/.gitignore": [ - "interface:untrusted-container-runtime", - "static", - "a2ebfecdb6c1b58267fbe97e6e2ac02c2b963df7673fc1047270f0f0cff16732" - ], - "hooks/relations/untrusted-container-runtime/LICENSE": [ - "interface:untrusted-container-runtime", - "static", - "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" - ], - "hooks/relations/untrusted-container-runtime/README.md": [ - "interface:untrusted-container-runtime", - "static", - "e3dc7db9ee98b716cb9a3a281fad88ca313bc11888a0da2f4b63c4306d91b64f" - ], - "hooks/relations/untrusted-container-runtime/__init__.py": [ - "interface:untrusted-container-runtime", - "static", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - ], - "hooks/relations/untrusted-container-runtime/interface.yaml": [ - "interface:untrusted-container-runtime", - "static", - "1fcb0305295206dc2b9926bf1870cae2c6cd8eee6eef72b6060c85e4f2109a45" - ], - "hooks/relations/untrusted-container-runtime/provides.py": [ - "interface:untrusted-container-runtime", - "static", - "05a52be7ad18df5cac9fb5dcc27c2ab24fe12e65fa809e0ea4d395dbcb36e6f2" - ], - "hooks/relations/untrusted-container-runtime/requires.py": [ - "interface:untrusted-container-runtime", - "static", - "958e03e254ee27bee761a6af3e032a273204b356dc51438489cde726b1a6e060" - ], - "hooks/start": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/stop": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/untrusted-relation-broken": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/untrusted-relation-changed": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/untrusted-relation-created": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/untrusted-relation-departed": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/untrusted-relation-joined": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/update-status": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "hooks/upgrade-charm": [ - "layer:basic", - "dynamic", - "2b693cb2a11594a80cc91235c2dc219a0a6303ae62bee8aa87eb35781f7158f7" - ], - "icon.svg": [ - "kata", - "static", - "d20624e9389af6506a8d8a69ac9bba4d41709601b624c0875fd7d6717b395088" - ], - "layer.yaml": [ - "kata", - "dynamic", - "599574e1d3dda3bf1d63047ac0b152caffcf22058e2f61370a37c8bb89317e4c" - ], - "lib/charms/layer/__init__.py": [ - "layer:basic", - "static", - "dfe0d26c6bf409767de6e2546bc648f150e1b396243619bad3aa0553ab7e0e6f" - ], - "lib/charms/layer/basic.py": [ - "layer:basic", - "static", - "3126b5754ad39402ee27e64527044ddd231ed1cd137fcedaffb51e63a635f108" - ], - "lib/charms/layer/execd.py": [ - "layer:basic", - "static", - "fda8bd491032db1db8ddaf4e99e7cc878c6fb5432efe1f91cadb5b34765d076d" - ], - "lib/charms/layer/options.py": [ - "layer:options", - "static", - "8ae7a07d22542fc964f2d2bee8219d1c78a68dace70a1b38d36d4aea47b1c3b2" - ], - "lib/charms/layer/status.py": [ - "layer:status", - "static", - "d560a5e07b2e5f2b0f25f30e1f0278b06f3f90c01e4dbad5c83d71efc79018c6" - ], - "lib/debug_script.py": [ - "layer:debug", - "static", - "a4d56f2d3e712b1b5cadb657c7195c6268d0aac6d228991049fd769e0ddaf453" - ], - "make_docs": [ - "layer:status", - "static", - "c990f55c8e879793a62ed8464ee3d7e0d7d2225fdecaf17af24b0df0e2daa8c1" - ], - "metadata.yaml": [ - "kata", - "dynamic", - "883f95d6180166d507365b3374b733fde27e0eb988d9532e88bb66e002c3fd68" - ], - "pydocmd.yml": [ - "layer:status", - "static", - "11d9293901f32f75f4256ae4ac2073b92ce1d7ef7b6c892ba9fbb98690a0b330" - ], - "reactive/__init__.py": [ - "layer:basic", - "static", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - ], - "reactive/kata.py": [ - "kata", - "static", - "7863484c83034271ea1f7a645c9f904405047db1be0fd7857f80008f47e073bf" - ], - "reactive/status.py": [ - "layer:status", - "static", - "30207fc206f24e91def5252f1c7f7c8e23c0aed0e93076babf5e03c05296d207" - ], - "requirements.txt": [ - "layer:basic", - "static", - "a00f75d80849e5b4fc5ad2e7536f947c25b1a4044b341caa8ee87a92d3a4c804" - ], - "tests/conftest.py": [ - "kata", - "static", - "fd53e0c38b4dda0c18096167889cd0d85b98b0a13225f9f8853261241e94078c" - ], - "tests/test_kata_reactive.py": [ - "kata", - "static", - "24d714d03b6f2c2faa67ecdbd7d102f700087973eb5c98d7b9c8e5542d61541c" - ], - "tox.ini": [ - "kata", - "static", - "b04898a3c4de3bf48ca4363751048ec83ed185bc27af7d956ae799d88d3827ab" - ], - "version": [ - "kata", - "dynamic", - "f6c325fd13ee5c726bc2e631996963198f2cfbaa50599b4962b151630fa86cf4" - ], - "wheelhouse.txt": [ - "kata", - "dynamic", - "425000e4406bf00f663cf41789c409e7980e4bd4a1b557b0470770502f71ed09" - ], - "wheelhouse/Jinja2-2.10.1.tar.gz": [ - "layer:basic", - "dynamic", - "065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013" - ], - "wheelhouse/MarkupSafe-1.1.1.tar.gz": [ - "layer:basic", - "dynamic", - "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b" - ], - "wheelhouse/PyYAML-5.2.tar.gz": [ - "layer:basic", - "dynamic", - "c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c" - ], - "wheelhouse/Tempita-0.5.2.tar.gz": [ - "__pip__", - "dynamic", - "cacecf0baa674d356641f1d406b8bff1d756d739c46b869a54de515d08e6fc9c" - ], - "wheelhouse/certifi-2021.5.30.tar.gz": [ - "__pip__", - "dynamic", - "2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee" - ], - "wheelhouse/charmhelpers-0.20.22.tar.gz": [ - "layer:basic", - "dynamic", - "b7550108118ce4f87488343384441797777d0da746e1346ed4e6361b4eab0ddb" - ], - "wheelhouse/charms.reactive-1.4.1.tar.gz": [ - "layer:basic", - "dynamic", - "bba21b4fd40b26c240c9ef2aa10c6fdf73592031c68591da4e7ccc46ca9cb616" - ], - "wheelhouse/charset-normalizer-2.0.3.tar.gz": [ - "__pip__", - "dynamic", - "c46c3ace2d744cfbdebceaa3c19ae691f53ae621b39fd7570f59d14fb7f2fd12" - ], - "wheelhouse/idna-3.2.tar.gz": [ - "__pip__", - "dynamic", - "467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" - ], - "wheelhouse/netaddr-0.7.19.tar.gz": [ - "layer:basic", - "dynamic", - "38aeec7cdd035081d3a4c306394b19d677623bf76fa0913f6695127c7753aefd" - ], - "wheelhouse/pbr-5.6.0.tar.gz": [ - "__pip__", - "dynamic", - "42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd" - ], - "wheelhouse/pip-18.1.tar.gz": [ - "layer:basic", - "dynamic", - "c0a292bd977ef590379a3f05d7b7f65135487b67470f6281289a94e015650ea1" - ], - "wheelhouse/pyaml-20.4.0.tar.gz": [ - "__pip__", - "dynamic", - "29a5c2a68660a799103d6949167bd6c7953d031449d08802386372de1db6ad71" - ], - "wheelhouse/requests-2.26.0.tar.gz": [ - "kata", - "dynamic", - "b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" - ], - "wheelhouse/setuptools-41.6.0.zip": [ - "layer:basic", - "dynamic", - "6afa61b391dcd16cb8890ec9f66cc4015a8a31a6e1c2b4e0c464514be1a3d722" - ], - "wheelhouse/setuptools_scm-1.17.0.tar.gz": [ - "layer:basic", - "dynamic", - "70a4cf5584e966ae92f54a764e6437af992ba42ac4bca7eb37cc5d02b98ec40a" - ], - "wheelhouse/six-1.16.0.tar.gz": [ - "__pip__", - "dynamic", - "1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926" - ], - "wheelhouse/urllib3-1.26.6.tar.gz": [ - "__pip__", - "dynamic", - "f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" - ], - "wheelhouse/wheel-0.33.6.tar.gz": [ - "layer:basic", - "dynamic", - "10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646" - ] - } -} \ No newline at end of file diff --git a/kata/.gitignore b/kata/.gitignore deleted file mode 100644 index 8003a31..0000000 --- a/kata/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -__pycache__/ -.coverage -.tox/ -.venv/ diff --git a/kata/.travis.yml b/kata/.travis.yml deleted file mode 100644 index 694ddcb..0000000 --- a/kata/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -dist: bionic -language: python -python: - - "3.5" - - "3.6" - - "3.7" - - "3.8" -install: - - pip install tox-travis -script: - - tox diff --git a/kata/.travis/profile-update.yaml b/kata/.travis/profile-update.yaml deleted file mode 100644 index 57f96eb..0000000 --- a/kata/.travis/profile-update.yaml +++ /dev/null @@ -1,12 +0,0 @@ -config: {} -description: Default LXD profile - updated -devices: - eth0: - name: eth0 - parent: lxdbr0 - nictype: bridged - type: nic - root: - path: / - pool: default - type: disk diff --git a/kata/CONTRIBUTING.md b/kata/CONTRIBUTING.md deleted file mode 100644 index 7a8f252..0000000 --- a/kata/CONTRIBUTING.md +++ /dev/null @@ -1,41 +0,0 @@ -# Contributor Guide - -This Juju charm is open source ([Apache License 2.0](./LICENSE)) and we actively seek any community contibutions -for code, suggestions and documentation. -This page details a few notes, workflows and suggestions for how to make contributions most effective and help us -all build a better charm - please give them a read before working on any contributions. - -## Licensing - -This charm has been created under the [Apache License 2.0](./LICENSE), which will cover any contributions you may -make to this project. Please familiarise yourself with the terms of the license. - -Additionally, this charm uses the Harmony CLA agreement. It’s the easiest way for you to give us permission to -use your contributions. -In effect, you’re giving us a license, but you still own the copyright — so you retain the right to modify your -code and use it in other projects. Please [sign the CLA here](https://ubuntu.com/legal/contributors/agreement) before -making any contributions. - -## Code of conduct - -We have adopted the Ubuntu code of Conduct. You can read this in full [here](https://ubuntu.com/community/code-of-conduct). - -## Contributing code - -To contribute code to this project, please use the following workflow: - -1. [Submit a bug][bug] to explain the need for and track the change. -2. Create a branch on your fork of the repo with your changes, including a unit test covering the new or modified code. -3. Submit a PR. The PR description should include a link to the bug on Launchpad. -4. Update the Launchpad bug to include a link to the PR and the `review-needed` tag. -5. Once reviewed and merged, the change will become available on the edge channel and assigned to an appropriate milestone - for further release according to priority. - -## Documentation - -Documentation for this charm is currently maintained as part of the Charmed Kubernetes docs. -See [this page][docs] - - -[bug]: https://bugs.launchpad.net/charm-kata/+filebug -[docs]: https://github.com/charmed-kubernetes/kubernetes-docs/blob/master/pages/k8s/charm-kata.md \ No newline at end of file diff --git a/kata/LICENSE b/kata/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/kata/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/kata/Makefile b/kata/Makefile deleted file mode 100644 index a1ad3a5..0000000 --- a/kata/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/make - -all: lint unit_test - - -.PHONY: clean -clean: - @rm -rf .tox - -.PHONY: apt_prereqs -apt_prereqs: - @# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip) - @which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox) - -.PHONY: lint -lint: apt_prereqs - @tox --notest - @PATH=.tox/py34/bin:.tox/py35/bin flake8 $(wildcard hooks reactive lib unit_tests tests) - @charm proof - -.PHONY: unit_test -unit_test: apt_prereqs - @echo Starting tests... - tox diff --git a/kata/README.md b/kata/README.md deleted file mode 100644 index 39cb53b..0000000 --- a/kata/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Charm for Kata Containers - -This subordinate charm deploys the [Kata](https://katacontainers.io/) -untrusted container runtime within a running Juju charm model. - -This charm is maintained along with the components of Charmed Kubernetes. -For full information, please visit the official [Charmed Kubernetes docs](https://ubuntu.com/kubernetes/docs/charm-kata). diff --git a/kata/actions.yaml b/kata/actions.yaml deleted file mode 100644 index 8712b6b..0000000 --- a/kata/actions.yaml +++ /dev/null @@ -1,2 +0,0 @@ -"debug": - "description": "Collect debug data" diff --git a/kata/actions/debug b/kata/actions/debug deleted file mode 100755 index 8ba160e..0000000 --- a/kata/actions/debug +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/local/sbin/charm-env python3 - -import os -import subprocess -import tarfile -import tempfile -import traceback -from contextlib import contextmanager -from datetime import datetime -from charmhelpers.core.hookenv import action_set, local_unit - -archive_dir = None -log_file = None - - -@contextmanager -def archive_context(): - """ Open a context with a new temporary directory. - - When the context closes, the directory is archived, and the archive - location is added to Juju action output. """ - global archive_dir - global log_file - with tempfile.TemporaryDirectory() as temp_dir: - name = "debug-" + datetime.now().strftime("%Y%m%d%H%M%S") - archive_dir = os.path.join(temp_dir, name) - os.makedirs(archive_dir) - with open("%s/debug.log" % archive_dir, "w") as log_file: - yield - os.chdir(temp_dir) - tar_path = "/home/ubuntu/%s.tar.gz" % name - with tarfile.open(tar_path, "w:gz") as f: - f.add(name) - action_set({ - "path": tar_path, - "command": "juju scp %s:%s ." % (local_unit(), tar_path), - "message": " ".join([ - "Archive has been created on unit %s." % local_unit(), - "Use the juju scp command to copy it to your local machine." - ]) - }) - - -def log(msg): - """ Log a message that will be included in the debug archive. - - Must be run within archive_context """ - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - for line in str(msg).splitlines(): - log_file.write(timestamp + " | " + line.rstrip() + "\n") - - -def run_script(script): - """ Run a single script. Must be run within archive_context """ - log("Running script: " + script) - script_dir = os.path.join(archive_dir, script) - os.makedirs(script_dir) - env = os.environ.copy() - env["PYTHONPATH"] = "lib" # allow same imports as reactive code - env["DEBUG_SCRIPT_DIR"] = script_dir - with open(script_dir + "/stdout", "w") as stdout: - with open(script_dir + "/stderr", "w") as stderr: - process = subprocess.Popen( - "debug-scripts/" + script, - stdout=stdout, stderr=stderr, env=env - ) - try: - exit_code = process.wait(timeout=300) - except subprocess.TimeoutExpired: - log("ERROR: still running, terminating") - process.terminate() - try: - exit_code = process.wait(timeout=10) - except subprocess.TimeoutExpired: - log("ERROR: still running, killing") - process.kill() - exit_code = process.wait(timeout=10) - if exit_code != 0: - log("ERROR: %s failed with exit code %d" % (script, exit_code)) - - -def run_all_scripts(): - """ Run all scripts. For the sake of robustness, log and ignore any - exceptions that occur. - - Must be run within archive_context """ - scripts = os.listdir("debug-scripts") - for script in scripts: - try: - run_script(script) - except: - log(traceback.format_exc()) - - -def main(): - """ Open an archive context and run all scripts. """ - with archive_context(): - run_all_scripts() - - -if __name__ == "__main__": - main() diff --git a/kata/bin/charm-env b/kata/bin/charm-env deleted file mode 100755 index d211ce9..0000000 --- a/kata/bin/charm-env +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/bash - -VERSION="1.0.0" - - -find_charm_dirs() { - # Hopefully, $JUJU_CHARM_DIR is set so which venv to use in unambiguous. - if [[ -n "$JUJU_CHARM_DIR" || -n "$CHARM_DIR" ]]; then - if [[ -z "$JUJU_CHARM_DIR" ]]; then - # accept $CHARM_DIR to be more forgiving - export JUJU_CHARM_DIR="$CHARM_DIR" - fi - if [[ -z "$CHARM_DIR" ]]; then - # set CHARM_DIR as well to help with backwards compatibility - export CHARM_DIR="$JUJU_CHARM_DIR" - fi - return - fi - # Try to guess the value for JUJU_CHARM_DIR by looking for a non-subordinate - # (because there's got to be at least one principle) charm directory; - # if there are several, pick the first by alpha order. - agents_dir="/var/lib/juju/agents" - if [[ -d "$agents_dir" ]]; then - desired_charm="$1" - found_charm_dir="" - if [[ -n "$desired_charm" ]]; then - for charm_dir in $(/bin/ls -d "$agents_dir"/unit-*/charm); do - charm_name="$(grep -o '^['\''"]\?name['\''"]\?:.*' $charm_dir/metadata.yaml 2> /dev/null | sed -e 's/.*: *//' -e 's/['\''"]//g')" - if [[ "$charm_name" == "$desired_charm" ]]; then - if [[ -n "$found_charm_dir" ]]; then - >&2 echo "Ambiguous possibilities for JUJU_CHARM_DIR matching '$desired_charm'; please run within a Juju hook context" - exit 1 - fi - found_charm_dir="$charm_dir" - fi - done - if [[ -z "$found_charm_dir" ]]; then - >&2 echo "Unable to determine JUJU_CHARM_DIR matching '$desired_charm'; please run within a Juju hook context" - exit 1 - fi - export JUJU_CHARM_DIR="$found_charm_dir" - export CHARM_DIR="$found_charm_dir" - return - fi - # shellcheck disable=SC2126 - non_subordinates="$(grep -L 'subordinate"\?:.*true' "$agents_dir"/unit-*/charm/metadata.yaml | wc -l)" - if [[ "$non_subordinates" -gt 1 ]]; then - >&2 echo 'Ambiguous possibilities for JUJU_CHARM_DIR; please use --charm or run within a Juju hook context' - exit 1 - elif [[ "$non_subordinates" -eq 1 ]]; then - for charm_dir in $(/bin/ls -d "$agents_dir"/unit-*/charm); do - if grep -q 'subordinate"\?:.*true' "$charm_dir/metadata.yaml"; then - continue - fi - export JUJU_CHARM_DIR="$charm_dir" - export CHARM_DIR="$charm_dir" - return - done - fi - fi - >&2 echo 'Unable to determine JUJU_CHARM_DIR; please run within a Juju hook context' - exit 1 -} - -try_activate_venv() { - if [[ -d "$JUJU_CHARM_DIR/../.venv" ]]; then - . "$JUJU_CHARM_DIR/../.venv/bin/activate" - fi -} - -find_wrapped() { - PATH="${PATH/\/usr\/local\/sbin:}" which "$(basename "$0")" -} - - -if [[ "$1" == "--version" || "$1" == "-v" ]]; then - echo "$VERSION" - exit 0 -fi - - -# allow --charm option to hint which JUJU_CHARM_DIR to choose when ambiguous -# NB: --charm option must come first -# NB: option must be processed outside find_charm_dirs to modify $@ -charm_name="" -if [[ "$1" == "--charm" ]]; then - charm_name="$2" - shift; shift -fi - -find_charm_dirs "$charm_name" -try_activate_venv -export PYTHONPATH="$JUJU_CHARM_DIR/lib:$PYTHONPATH" - -if [[ "$(basename "$0")" == "charm-env" ]]; then - # being used as a shebang - exec "$@" -elif [[ "$0" == "$BASH_SOURCE" ]]; then - # being invoked as a symlink wrapping something to find in the venv - exec "$(find_wrapped)" "$@" -elif [[ "$(basename "$BASH_SOURCE")" == "charm-env" ]]; then - # being sourced directly; do nothing - /bin/true -else - # being sourced for wrapped bash helpers - . "$(find_wrapped)" -fi diff --git a/kata/bin/layer_option b/kata/bin/layer_option deleted file mode 100755 index 3253ef8..0000000 --- a/kata/bin/layer_option +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import argparse -from charms import layer - - -parser = argparse.ArgumentParser(description='Access layer options.') -parser.add_argument('section', - help='the section, or layer, the option is from') -parser.add_argument('option', - help='the option to access') - -args = parser.parse_args() -value = layer.options.get(args.section, args.option) -if isinstance(value, bool): - sys.exit(0 if value else 1) -elif isinstance(value, list): - for val in value: - print(val) -else: - print(value) diff --git a/kata/copyright b/kata/copyright deleted file mode 100644 index a91bdf1..0000000 --- a/kata/copyright +++ /dev/null @@ -1,16 +0,0 @@ -Format: http://dep.debian.net/deps/dep5/ - -Files: * -Copyright: Copyright 2018, Canonical Ltd., All Rights Reserved. -License: Apache License 2.0 - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - . - http://www.apache.org/licenses/LICENSE-2.0 - . - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/kata/copyright.layer-basic b/kata/copyright.layer-basic deleted file mode 100644 index d4fdd18..0000000 --- a/kata/copyright.layer-basic +++ /dev/null @@ -1,16 +0,0 @@ -Format: http://dep.debian.net/deps/dep5/ - -Files: * -Copyright: Copyright 2015-2017, Canonical Ltd., All Rights Reserved. -License: Apache License 2.0 - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - . - http://www.apache.org/licenses/LICENSE-2.0 - . - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/kata/copyright.layer-options b/kata/copyright.layer-options deleted file mode 100644 index d4fdd18..0000000 --- a/kata/copyright.layer-options +++ /dev/null @@ -1,16 +0,0 @@ -Format: http://dep.debian.net/deps/dep5/ - -Files: * -Copyright: Copyright 2015-2017, Canonical Ltd., All Rights Reserved. -License: Apache License 2.0 - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - . - http://www.apache.org/licenses/LICENSE-2.0 - . - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/kata/debug-scripts/charm-unitdata b/kata/debug-scripts/charm-unitdata deleted file mode 100755 index d2aac60..0000000 --- a/kata/debug-scripts/charm-unitdata +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/local/sbin/charm-env python3 - -import debug_script -import json -from charmhelpers.core import unitdata - -kv = unitdata.kv() -data = kv.getrange("") - -with debug_script.open_file("unitdata.json", "w") as f: - json.dump(data, f, indent=2) - f.write("\n") diff --git a/kata/debug-scripts/filesystem b/kata/debug-scripts/filesystem deleted file mode 100755 index c5ec6d8..0000000 --- a/kata/debug-scripts/filesystem +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -set -ux - -# report file system disk space usage -df -hT > $DEBUG_SCRIPT_DIR/df-hT -# estimate file space usage -du -h / 2>&1 > $DEBUG_SCRIPT_DIR/du-h -# list the mounted filesystems -mount > $DEBUG_SCRIPT_DIR/mount -# list the mounted systems with ascii trees -findmnt -A > $DEBUG_SCRIPT_DIR/findmnt -# list block devices -lsblk > $DEBUG_SCRIPT_DIR/lsblk -# list open files -lsof 2>&1 > $DEBUG_SCRIPT_DIR/lsof -# list local system locks -lslocks > $DEBUG_SCRIPT_DIR/lslocks diff --git a/kata/debug-scripts/juju-logs b/kata/debug-scripts/juju-logs deleted file mode 100755 index d27c458..0000000 --- a/kata/debug-scripts/juju-logs +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -set -ux - -cp -v /var/log/juju/* $DEBUG_SCRIPT_DIR diff --git a/kata/debug-scripts/juju-network-get b/kata/debug-scripts/juju-network-get deleted file mode 100755 index 983c8c4..0000000 --- a/kata/debug-scripts/juju-network-get +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/local/sbin/charm-env python3 - -import os -import subprocess -import yaml -import debug_script - -with open('metadata.yaml') as f: - metadata = yaml.load(f) - -relations = [] -for key in ['requires', 'provides', 'peers']: - relations += list(metadata.get(key, {}).keys()) - -os.mkdir(os.path.join(debug_script.dir, 'relations')) - -for relation in relations: - path = 'relations/' + relation - with debug_script.open_file(path, 'w') as f: - cmd = ['network-get', relation] - subprocess.call(cmd, stdout=f, stderr=subprocess.STDOUT) diff --git a/kata/debug-scripts/network b/kata/debug-scripts/network deleted file mode 100755 index 944a355..0000000 --- a/kata/debug-scripts/network +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -set -ux - -ifconfig -a > $DEBUG_SCRIPT_DIR/ifconfig -cp -v /etc/resolv.conf $DEBUG_SCRIPT_DIR/resolv.conf -cp -v /etc/network/interfaces $DEBUG_SCRIPT_DIR/interfaces -netstat -planut > $DEBUG_SCRIPT_DIR/netstat -route -n > $DEBUG_SCRIPT_DIR/route -iptables-save > $DEBUG_SCRIPT_DIR/iptables-save -dig google.com > $DEBUG_SCRIPT_DIR/dig-google -ping -w 2 -i 0.1 google.com > $DEBUG_SCRIPT_DIR/ping-google diff --git a/kata/debug-scripts/packages b/kata/debug-scripts/packages deleted file mode 100755 index b60a9cf..0000000 --- a/kata/debug-scripts/packages +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -set -ux - -dpkg --list > $DEBUG_SCRIPT_DIR/dpkg-list -snap list > $DEBUG_SCRIPT_DIR/snap-list -pip2 list > $DEBUG_SCRIPT_DIR/pip2-list -pip3 list > $DEBUG_SCRIPT_DIR/pip3-list diff --git a/kata/debug-scripts/sysctl b/kata/debug-scripts/sysctl deleted file mode 100755 index a86a6c8..0000000 --- a/kata/debug-scripts/sysctl +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -set -ux - -sysctl -a > $DEBUG_SCRIPT_DIR/sysctl diff --git a/kata/debug-scripts/systemd b/kata/debug-scripts/systemd deleted file mode 100755 index 8bb9b6f..0000000 --- a/kata/debug-scripts/systemd +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -set -ux - -systemctl --all > $DEBUG_SCRIPT_DIR/systemctl -journalctl > $DEBUG_SCRIPT_DIR/journalctl -systemd-analyze time > $DEBUG_SCRIPT_DIR/systemd-analyze-time -systemd-analyze blame > $DEBUG_SCRIPT_DIR/systemd-analyze-blame -systemd-analyze critical-chain > $DEBUG_SCRIPT_DIR/systemd-analyze-critical-chain -systemd-analyze dump > $DEBUG_SCRIPT_DIR/systemd-analyze-dump diff --git a/kata/docs/status.md b/kata/docs/status.md deleted file mode 100644 index c6cceab..0000000 --- a/kata/docs/status.md +++ /dev/null @@ -1,91 +0,0 @@ -

WorkloadState

- -```python -WorkloadState(self, /, *args, **kwargs) -``` - -Enum of the valid workload states. - -Valid options are: - - * `WorkloadState.MAINTENANCE` - * `WorkloadState.BLOCKED` - * `WorkloadState.WAITING` - * `WorkloadState.ACTIVE` - -

maintenance

- -```python -maintenance(message) -``` - -Set the status to the `MAINTENANCE` state with the given operator message. - -__Parameters__ - -- __`message` (str)__: Message to convey to the operator. - -

maint

- -```python -maint(message) -``` - -Shorthand alias for -[maintenance](status.md#charms.layer.status.maintenance). - -__Parameters__ - -- __`message` (str)__: Message to convey to the operator. - -

blocked

- -```python -blocked(message) -``` - -Set the status to the `BLOCKED` state with the given operator message. - -__Parameters__ - -- __`message` (str)__: Message to convey to the operator. - -

waiting

- -```python -waiting(message) -``` - -Set the status to the `WAITING` state with the given operator message. - -__Parameters__ - -- __`message` (str)__: Message to convey to the operator. - -

active

- -```python -active(message) -``` - -Set the status to the `ACTIVE` state with the given operator message. - -__Parameters__ - -- __`message` (str)__: Message to convey to the operator. - -

status_set

- -```python -status_set(workload_state, message) -``` - -Set the status to the given workload state with a message. - -__Parameters__ - -- __`workload_state` (WorkloadState or str)__: State of the workload. Should be - a [WorkloadState](status.md#charms.layer.status.WorkloadState) enum - member, or the string value of one of those members. -- __`message` (str)__: Message to convey to the operator. - diff --git a/kata/hooks/config-changed b/kata/hooks/config-changed deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/config-changed +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/containerd-relation-broken b/kata/hooks/containerd-relation-broken deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/containerd-relation-broken +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/containerd-relation-changed b/kata/hooks/containerd-relation-changed deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/containerd-relation-changed +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/containerd-relation-created b/kata/hooks/containerd-relation-created deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/containerd-relation-created +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/containerd-relation-departed b/kata/hooks/containerd-relation-departed deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/containerd-relation-departed +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/containerd-relation-joined b/kata/hooks/containerd-relation-joined deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/containerd-relation-joined +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/hook.template b/kata/hooks/hook.template deleted file mode 100644 index 9858c6b..0000000 --- a/kata/hooks/hook.template +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/install b/kata/hooks/install deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/install +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/leader-elected b/kata/hooks/leader-elected deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/leader-elected +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/leader-settings-changed b/kata/hooks/leader-settings-changed deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/leader-settings-changed +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/post-series-upgrade b/kata/hooks/post-series-upgrade deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/post-series-upgrade +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/pre-series-upgrade b/kata/hooks/pre-series-upgrade deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/pre-series-upgrade +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/relations/container-runtime/.gitignore b/kata/hooks/relations/container-runtime/.gitignore deleted file mode 100644 index 894a44c..0000000 --- a/kata/hooks/relations/container-runtime/.gitignore +++ /dev/null @@ -1,104 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ diff --git a/kata/hooks/relations/container-runtime/LICENSE b/kata/hooks/relations/container-runtime/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/kata/hooks/relations/container-runtime/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/kata/hooks/relations/container-runtime/README.md b/kata/hooks/relations/container-runtime/README.md deleted file mode 100644 index 4620013..0000000 --- a/kata/hooks/relations/container-runtime/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# interface-container-runtime - -## Overview - -This interface handles communication between subordinate charms, that provide a container runtime and charms requiring a container runtime. - -## Usage - -### Provides - -The providing side of the container interface provides a place for a container runtime to connect to. - -Your charm should respond to the `endpoint.{endpoint_name}.available` state, -which indicates that there is a container runtime connected. - -A trivial example of handling this interface would be: - -```python -@when('endpoint.containerd.joined') -def update_kubelet_config(containerd): - endpoint = endpoint_from_flag('endpoint.containerd.joined') - config = endpoint.get_config() - kubelet.config['container-runtime'] = \ - config['runtime'] -``` - -### Requires - -The requiring side of the container interface requires a place for a container runtime to connect to. - -Your charm should set `{endpoint_name}.available` state, -which indicates that the container is runtime connected. - -A trivial example of handling this interface would be: - -```python -@when('endpoint.containerd.joined') -def pubish_config(): - endpoint = endpoint_from_flag('endpoint.containerd.joined') - endpoint.set_config( - socket='unix:///var/run/containerd/containerd.sock', - runtime='remote', - nvidia_enabled=False - ) -``` diff --git a/kata/hooks/relations/container-runtime/__init__.py b/kata/hooks/relations/container-runtime/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kata/hooks/relations/container-runtime/interface.yaml b/kata/hooks/relations/container-runtime/interface.yaml deleted file mode 100644 index 294be1e..0000000 --- a/kata/hooks/relations/container-runtime/interface.yaml +++ /dev/null @@ -1,4 +0,0 @@ -name: container-runtime -summary: Interface for relating to container runtimes -version: 1 -maintainer: "Joe Borg " diff --git a/kata/hooks/relations/container-runtime/provides.py b/kata/hooks/relations/container-runtime/provides.py deleted file mode 100644 index a9768a8..0000000 --- a/kata/hooks/relations/container-runtime/provides.py +++ /dev/null @@ -1,55 +0,0 @@ -from charms.reactive import ( - Endpoint, - toggle_flag -) - - -class ContainerRuntimeProvides(Endpoint): - def manage_flags(self): - toggle_flag(self.expand_name('endpoint.{endpoint_name}.available'), - self.is_joined) - - def _get_config(self, key): - """ - Get the published configuration for a given key. - - :param key: String dict key - :return: String value for given key - """ - return self.all_joined_units.received.get(key) - - def get_nvidia_enabled(self): - """ - Get the published nvidia config. - - :return: String - """ - return self._get_config(key='nvidia_enabled') - - def get_runtime(self): - """ - Get the published runtime config. - - :return: String - """ - return self._get_config(key='runtime') - - def get_socket(self): - """ - Get the published socket config. - - :return: String - """ - return self._get_config(key='socket') - - def set_config(self, sandbox_image=None): - """ - Set the configuration to be published. - - :param sandbox_image: String to optionally override the sandbox image - :return: None - """ - for relation in self.relations: - relation.to_publish.update({ - 'sandbox_image': sandbox_image - }) diff --git a/kata/hooks/relations/container-runtime/requires.py b/kata/hooks/relations/container-runtime/requires.py deleted file mode 100644 index c461b68..0000000 --- a/kata/hooks/relations/container-runtime/requires.py +++ /dev/null @@ -1,61 +0,0 @@ -from charms.reactive import ( - Endpoint, - clear_flag, - data_changed, - is_data_changed, - toggle_flag -) - - -class ContainerRuntimeRequires(Endpoint): - def manage_flags(self): - toggle_flag(self.expand_name('endpoint.{endpoint_name}.available'), - self.is_joined) - toggle_flag(self.expand_name('endpoint.{endpoint_name}.reconfigure'), - self.is_joined and self._config_changed()) - - def _config_changed(self): - """ - Determine if our received data has changed. - - :return: Boolean - """ - # NB: this call should match whatever we're tracking in handle_remote_config - return is_data_changed('containerd.remote_config', - [self.get_sandbox_image()]) - - def handle_remote_config(self): - """ - Keep track of received data so we can know if it changes. - - :return: None - """ - clear_flag(self.expand_name('endpoint.{endpoint_name}.reconfigure')) - # Presently, we only care about one piece of remote config. Expand - # the list as needed. - data_changed('containerd.remote_config', - [self.get_sandbox_image()]) - - def get_sandbox_image(self): - """ - Get the sandbox image URI if a remote has published one. - - :return: String: remotely configured sandbox image - """ - return self.all_joined_units.received.get('sandbox_image') - - def set_config(self, socket, runtime, nvidia_enabled): - """ - Set the configuration to be published. - - :param socket: String uri to runtime socket - :param runtime: String runtime executable - :param nvidia_enabled: Boolean nvidia runtime enabled - :return: None - """ - for relation in self.relations: - relation.to_publish.update({ - 'socket': socket, - 'runtime': runtime, - 'nvidia_enabled': nvidia_enabled - }) diff --git a/kata/hooks/relations/untrusted-container-runtime/.gitignore b/kata/hooks/relations/untrusted-container-runtime/.gitignore deleted file mode 100644 index 894a44c..0000000 --- a/kata/hooks/relations/untrusted-container-runtime/.gitignore +++ /dev/null @@ -1,104 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ diff --git a/kata/hooks/relations/untrusted-container-runtime/LICENSE b/kata/hooks/relations/untrusted-container-runtime/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/kata/hooks/relations/untrusted-container-runtime/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/kata/hooks/relations/untrusted-container-runtime/README.md b/kata/hooks/relations/untrusted-container-runtime/README.md deleted file mode 100644 index 135dca5..0000000 --- a/kata/hooks/relations/untrusted-container-runtime/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# interface-untrusted-container-runtime - -## Overview - -This interface handles communication between subordinate container runtimes -and this subordinate untrusted container runtime, such as `containerd` and -`kata-containers`. - -## Usage - -### Provides - -The providing side of the container interface provides a place for an -untrusted container runtime to connect to. - -Your charm should respond to the `endpoint.{endpoint_name}.available` state, -which indicates that there is an untrusted container runtime connected. - -A trivial example of handling this interface would be: - -```python -@when('endpoint.containerd.joined') -def update_kubelet_config(containerd): - endpoint = endpoint_from_flag('endpoint.containerd.joined') - config = endpoint.get_config() - - render( - 'config.toml', - { - 'runtime_name': config['name'], - 'runtime_binary': config['binary_path'] - } - ) -``` - -### Requires - -The requiring side of the untrusted container interface requires a place for -an untrusted container runtime to connect to. - -Your charm should set `{endpoint_name}.available` state, -which indicates that the container is runtime connected. - -A trivial example of handling this interface would be: - -```python -@when('endpoint.containerd.joined') -def pubish_config(): - endpoint = endpoint_from_flag('endpoint.containerd.joined') - endpoint.set_config( - 'name': 'kata', - 'binary_path': '/usr/bin/kata-runtime' - ) -``` diff --git a/kata/hooks/relations/untrusted-container-runtime/__init__.py b/kata/hooks/relations/untrusted-container-runtime/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kata/hooks/relations/untrusted-container-runtime/interface.yaml b/kata/hooks/relations/untrusted-container-runtime/interface.yaml deleted file mode 100644 index d0d7dbc..0000000 --- a/kata/hooks/relations/untrusted-container-runtime/interface.yaml +++ /dev/null @@ -1,4 +0,0 @@ -name: untrusted-container-runtime -summary: Interface for relating to untrusted container runtimes -version: 1 -maintainer: "Joe Borg " diff --git a/kata/hooks/relations/untrusted-container-runtime/provides.py b/kata/hooks/relations/untrusted-container-runtime/provides.py deleted file mode 100644 index 09deb26..0000000 --- a/kata/hooks/relations/untrusted-container-runtime/provides.py +++ /dev/null @@ -1,28 +0,0 @@ -from charms.reactive import ( - Endpoint, - set_flag, - clear_flag -) - -from charms.reactive import ( - when, - when_not -) - - -class ContainerRuntimeProvides(Endpoint): - @when('endpoint.{endpoint_name}.joined') - def joined(self): - set_flag(self.expand_name('endpoint.{endpoint_name}.available')) - - @when_not('endpoint.{endpoint_name}.joined') - def broken(self): - clear_flag(self.expand_name('endpoint.{endpoint_name}.available')) - - def get_config(self): - """ - Get the configuration published. - - :return: Dictionary configuration - """ - return self.all_joined_units.received diff --git a/kata/hooks/relations/untrusted-container-runtime/requires.py b/kata/hooks/relations/untrusted-container-runtime/requires.py deleted file mode 100644 index f717ba6..0000000 --- a/kata/hooks/relations/untrusted-container-runtime/requires.py +++ /dev/null @@ -1,34 +0,0 @@ -from charms.reactive import ( - Endpoint, - set_flag, - clear_flag -) - -from charms.reactive import ( - when, - when_not -) - - -class ContainerRuntimeRequires(Endpoint): - @when('endpoint.{endpoint_name}.changed') - def changed(self): - set_flag(self.expand_name('endpoint.{endpoint_name}.available')) - - @when_not('endpoint.{endpoint_name}.joined') - def broken(self): - clear_flag(self.expand_name('endpoint.{endpoint_name}.available')) - - def set_config(self, name, binary_path): - """ - Set the configuration to be published. - - :param name: String name of runtime - :param binary_path: String runtime executable - :return: None - """ - for relation in self.relations: - relation.to_publish.update({ - 'name': name, - 'binary_path': binary_path - }) diff --git a/kata/hooks/start b/kata/hooks/start deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/start +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/stop b/kata/hooks/stop deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/stop +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/untrusted-relation-broken b/kata/hooks/untrusted-relation-broken deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/untrusted-relation-broken +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/untrusted-relation-changed b/kata/hooks/untrusted-relation-changed deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/untrusted-relation-changed +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/untrusted-relation-created b/kata/hooks/untrusted-relation-created deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/untrusted-relation-created +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/untrusted-relation-departed b/kata/hooks/untrusted-relation-departed deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/untrusted-relation-departed +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/untrusted-relation-joined b/kata/hooks/untrusted-relation-joined deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/untrusted-relation-joined +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/update-status b/kata/hooks/update-status deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/update-status +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/hooks/upgrade-charm b/kata/hooks/upgrade-charm deleted file mode 100755 index 9858c6b..0000000 --- a/kata/hooks/upgrade-charm +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python3 - -# Load modules from $JUJU_CHARM_DIR/lib -import sys -sys.path.append('lib') - -from charms.layer import basic # noqa -basic.bootstrap_charm_deps() - -from charmhelpers.core import hookenv # noqa -hookenv.atstart(basic.init_config_states) -hookenv.atexit(basic.clear_config_states) - - -# This will load and run the appropriate @hook and other decorated -# handlers from $JUJU_CHARM_DIR/reactive, $JUJU_CHARM_DIR/hooks/reactive, -# and $JUJU_CHARM_DIR/hooks/relations. -# -# See https://jujucharms.com/docs/stable/authors-charm-building -# for more information on this pattern. -from charms.reactive import main # noqa -main() diff --git a/kata/icon.svg b/kata/icon.svg deleted file mode 100644 index 96a5d0c..0000000 --- a/kata/icon.svg +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - diff --git a/kata/layer.yaml b/kata/layer.yaml deleted file mode 100644 index 7dfb411..0000000 --- a/kata/layer.yaml +++ /dev/null @@ -1,20 +0,0 @@ -"includes": -- "layer:options" -- "layer:basic" -- "layer:debug" -- "interface:container-runtime" -- "interface:untrusted-container-runtime" -- "layer:status" -"exclude": [".travis.yml", "tests", "tox.ini", "test-requirements.txt", "unit_tests"] -"options": - "basic": - "packages": [] - "python_packages": [] - "use_venv": !!bool "true" - "include_system_packages": !!bool "false" - "debug": {} - "status": - "patch-hookenv": !!bool "true" - "kata": {} -"repo": "https://github.com/charmed-kubernetes/charm-kata" -"is": "kata" diff --git a/kata/lib/charms/layer/__init__.py b/kata/lib/charms/layer/__init__.py deleted file mode 100644 index a8e0c64..0000000 --- a/kata/lib/charms/layer/__init__.py +++ /dev/null @@ -1,60 +0,0 @@ -import sys -from importlib import import_module -from pathlib import Path - - -def import_layer_libs(): - """ - Ensure that all layer libraries are imported. - - This makes it possible to do the following: - - from charms import layer - - layer.foo.do_foo_thing() - - Note: This function must be called after bootstrap. - """ - for module_file in Path('lib/charms/layer').glob('*'): - module_name = module_file.stem - if module_name in ('__init__', 'basic', 'execd') or not ( - module_file.suffix == '.py' or module_file.is_dir() - ): - continue - import_module('charms.layer.{}'.format(module_name)) - - -# Terrible hack to support the old terrible interface. -# Try to get people to call layer.options.get() instead so -# that we can remove this garbage. -# Cribbed from https://stackoverfLow.com/a/48100440/4941864 -class OptionsBackwardsCompatibilityHack(sys.modules[__name__].__class__): - def __call__(self, section=None, layer_file=None): - if layer_file is None: - return self.get(section=section) - else: - return self.get(section=section, - layer_file=Path(layer_file)) - - -def patch_options_interface(): - from charms.layer import options - if sys.version_info.minor >= 5: - options.__class__ = OptionsBackwardsCompatibilityHack - else: - # Py 3.4 doesn't support changing the __class__, so we have to do it - # another way. The last line is needed because we already have a - # reference that doesn't get updated with sys.modules. - name = options.__name__ - hack = OptionsBackwardsCompatibilityHack(name) - hack.get = options.get - sys.modules[name] = hack - sys.modules[__name__].options = hack - - -try: - patch_options_interface() -except ImportError: - # This may fail if pyyaml hasn't been installed yet. But in that - # case, the bootstrap logic will try it again once it has. - pass diff --git a/kata/lib/charms/layer/basic.py b/kata/lib/charms/layer/basic.py deleted file mode 100644 index 7507203..0000000 --- a/kata/lib/charms/layer/basic.py +++ /dev/null @@ -1,446 +0,0 @@ -import os -import sys -import re -import shutil -from distutils.version import LooseVersion -from pkg_resources import Requirement -from glob import glob -from subprocess import check_call, check_output, CalledProcessError -from time import sleep - -from charms import layer -from charms.layer.execd import execd_preinstall - - -def _get_subprocess_env(): - env = os.environ.copy() - env['LANG'] = env.get('LANG', 'C.UTF-8') - return env - - -def get_series(): - """ - Return series for a few known OS:es. - Tested as of 2019 november: - * centos6, centos7, rhel6. - * bionic - """ - series = "" - - # Looking for content in /etc/os-release - # works for ubuntu + some centos - if os.path.isfile('/etc/os-release'): - d = {} - with open('/etc/os-release', 'r') as rel: - for l in rel: - if not re.match(r'^\s*$', l): - k, v = l.split('=') - d[k.strip()] = v.strip().replace('"', '') - series = "{ID}{VERSION_ID}".format(**d) - - # Looking for content in /etc/redhat-release - # works for redhat enterprise systems - elif os.path.isfile('/etc/redhat-release'): - with open('/etc/redhat-release', 'r') as redhatlsb: - # CentOS Linux release 7.7.1908 (Core) - line = redhatlsb.readline() - release = int(line.split("release")[1].split()[0][0]) - series = "centos" + str(release) - - # Looking for content in /etc/lsb-release - # works for ubuntu - elif os.path.isfile('/etc/lsb-release'): - d = {} - with open('/etc/lsb-release', 'r') as lsb: - for l in lsb: - k, v = l.split('=') - d[k.strip()] = v.strip() - series = d['DISTRIB_CODENAME'] - - # This is what happens if we cant figure out the OS. - else: - series = "unknown" - return series - - -def bootstrap_charm_deps(): - """ - Set up the base charm dependencies so that the reactive system can run. - """ - # execd must happen first, before any attempt to install packages or - # access the network, because sites use this hook to do bespoke - # configuration and install secrets so the rest of this bootstrap - # and the charm itself can actually succeed. This call does nothing - # unless the operator has created and populated $JUJU_CHARM_DIR/exec.d. - execd_preinstall() - # ensure that $JUJU_CHARM_DIR/bin is on the path, for helper scripts - - series = get_series() - - # OMG?! is build-essentials needed? - ubuntu_packages = ['python3-pip', - 'python3-setuptools', - 'python3-yaml', - 'python3-dev', - 'python3-wheel', - 'build-essential'] - - # I'm not going to "yum group info "Development Tools" - # omitting above madness - centos_packages = ['python3-pip', - 'python3-setuptools', - 'python3-devel', - 'python3-wheel'] - - packages_needed = [] - if 'centos' in series: - packages_needed = centos_packages - else: - packages_needed = ubuntu_packages - - charm_dir = os.environ['JUJU_CHARM_DIR'] - os.environ['PATH'] += ':%s' % os.path.join(charm_dir, 'bin') - venv = os.path.abspath('../.venv') - vbin = os.path.join(venv, 'bin') - vpip = os.path.join(vbin, 'pip') - vpy = os.path.join(vbin, 'python') - hook_name = os.path.basename(sys.argv[0]) - is_bootstrapped = os.path.exists('wheelhouse/.bootstrapped') - is_charm_upgrade = hook_name == 'upgrade-charm' - is_series_upgrade = hook_name == 'post-series-upgrade' - is_post_upgrade = os.path.exists('wheelhouse/.upgraded') - is_upgrade = (not is_post_upgrade and - (is_charm_upgrade or is_series_upgrade)) - if is_bootstrapped and not is_upgrade: - # older subordinates might have downgraded charm-env, so we should - # restore it if necessary - install_or_update_charm_env() - activate_venv() - # the .upgrade file prevents us from getting stuck in a loop - # when re-execing to activate the venv; at this point, we've - # activated the venv, so it's safe to clear it - if is_post_upgrade: - os.unlink('wheelhouse/.upgraded') - return - if os.path.exists(venv): - try: - # focal installs or upgrades prior to PR 160 could leave the venv - # in a broken state which would prevent subsequent charm upgrades - _load_installed_versions(vpip) - except CalledProcessError: - is_broken_venv = True - else: - is_broken_venv = False - if is_upgrade or is_broken_venv: - # All upgrades should do a full clear of the venv, rather than - # just updating it, to bring in updates to Python itself - shutil.rmtree(venv) - if is_upgrade: - if os.path.exists('wheelhouse/.bootstrapped'): - os.unlink('wheelhouse/.bootstrapped') - # bootstrap wheelhouse - if os.path.exists('wheelhouse'): - pre_eoan = series in ('ubuntu12.04', 'precise', - 'ubuntu14.04', 'trusty', - 'ubuntu16.04', 'xenial', - 'ubuntu18.04', 'bionic') - pydistutils_lines = [ - "[easy_install]\n", - "find_links = file://{}/wheelhouse/\n".format(charm_dir), - "no_index=True\n", - "index_url=\n", # deliberately nothing here; disables it. - ] - if pre_eoan: - pydistutils_lines.append("allow_hosts = ''\n") - with open('/root/.pydistutils.cfg', 'w') as fp: - # make sure that easy_install also only uses the wheelhouse - # (see https://github.com/pypa/pip/issues/410) - fp.writelines(pydistutils_lines) - if 'centos' in series: - yum_install(packages_needed) - else: - apt_install(packages_needed) - from charms.layer import options - cfg = options.get('basic') - # include packages defined in layer.yaml - if 'centos' in series: - yum_install(cfg.get('packages', [])) - else: - apt_install(cfg.get('packages', [])) - # if we're using a venv, set it up - if cfg.get('use_venv'): - if not os.path.exists(venv): - series = get_series() - if series in ('ubuntu12.04', 'precise', - 'ubuntu14.04', 'trusty'): - apt_install(['python-virtualenv']) - elif 'centos' in series: - yum_install(['python-virtualenv']) - else: - apt_install(['virtualenv']) - cmd = ['virtualenv', '-ppython3', '--never-download', venv] - if cfg.get('include_system_packages'): - cmd.append('--system-site-packages') - check_call(cmd, env=_get_subprocess_env()) - os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']]) - pip = vpip - else: - pip = 'pip3' - # save a copy of system pip to prevent `pip3 install -U pip` - # from changing it - if os.path.exists('/usr/bin/pip'): - shutil.copy2('/usr/bin/pip', '/usr/bin/pip.save') - pre_install_pkgs = ['pip', 'setuptools', 'setuptools-scm'] - # we bundle these packages to work around bugs in older versions (such - # as https://github.com/pypa/pip/issues/56), but if the system already - # provided a newer version, downgrading it can cause other problems - _update_if_newer(pip, pre_install_pkgs) - # install the rest of the wheelhouse deps (extract the pkg names into - # a set so that we can ignore the pre-install packages and let pip - # choose the best version in case there are multiple from layer - # conflicts) - pkgs = _load_wheelhouse_versions().keys() - set(pre_install_pkgs) - reinstall_flag = '--force-reinstall' - if not cfg.get('use_venv', True) and pre_eoan: - reinstall_flag = '--ignore-installed' - check_call([pip, 'install', '-U', reinstall_flag, '--no-index', - '--no-cache-dir', '-f', 'wheelhouse'] + list(pkgs), - env=_get_subprocess_env()) - # re-enable installation from pypi - os.remove('/root/.pydistutils.cfg') - - # install pyyaml for centos7, since, unlike the ubuntu image, the - # default image for centos doesn't include pyyaml; see the discussion: - # https://discourse.jujucharms.com/t/charms-for-centos-lets-begin - if 'centos' in series: - check_call([pip, 'install', '-U', 'pyyaml'], - env=_get_subprocess_env()) - - # install python packages from layer options - if cfg.get('python_packages'): - check_call([pip, 'install', '-U'] + cfg.get('python_packages'), - env=_get_subprocess_env()) - if not cfg.get('use_venv'): - # restore system pip to prevent `pip3 install -U pip` - # from changing it - if os.path.exists('/usr/bin/pip.save'): - shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip') - os.remove('/usr/bin/pip.save') - # setup wrappers to ensure envs are used for scripts - install_or_update_charm_env() - for wrapper in ('charms.reactive', 'charms.reactive.sh', - 'chlp', 'layer_option'): - src = os.path.join('/usr/local/sbin', 'charm-env') - dst = os.path.join('/usr/local/sbin', wrapper) - if not os.path.exists(dst): - os.symlink(src, dst) - if cfg.get('use_venv'): - shutil.copy2('bin/layer_option', vbin) - else: - shutil.copy2('bin/layer_option', '/usr/local/bin/') - # re-link the charm copy to the wrapper in case charms - # call bin/layer_option directly (as was the old pattern) - os.remove('bin/layer_option') - os.symlink('/usr/local/sbin/layer_option', 'bin/layer_option') - # flag us as having already bootstrapped so we don't do it again - open('wheelhouse/.bootstrapped', 'w').close() - if is_upgrade: - # flag us as having already upgraded so we don't do it again - open('wheelhouse/.upgraded', 'w').close() - # Ensure that the newly bootstrapped libs are available. - # Note: this only seems to be an issue with namespace packages. - # Non-namespace-package libs (e.g., charmhelpers) are available - # without having to reload the interpreter. :/ - reload_interpreter(vpy if cfg.get('use_venv') else sys.argv[0]) - - -def _load_installed_versions(pip): - pip_freeze = check_output([pip, 'freeze']).decode('utf8') - versions = {} - for pkg_ver in pip_freeze.splitlines(): - try: - req = Requirement.parse(pkg_ver) - except ValueError: - continue - versions.update({ - req.project_name: LooseVersion(ver) - for op, ver in req.specs if op == '==' - }) - return versions - - -def _load_wheelhouse_versions(): - versions = {} - for wheel in glob('wheelhouse/*'): - pkg, ver = os.path.basename(wheel).rsplit('-', 1) - # nb: LooseVersion ignores the file extension - versions[pkg.replace('_', '-')] = LooseVersion(ver) - return versions - - -def _update_if_newer(pip, pkgs): - installed = _load_installed_versions(pip) - wheelhouse = _load_wheelhouse_versions() - for pkg in pkgs: - if pkg not in installed or wheelhouse[pkg] > installed[pkg]: - check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse', - pkg], env=_get_subprocess_env()) - - -def install_or_update_charm_env(): - # On Trusty python3-pkg-resources is not installed - try: - from pkg_resources import parse_version - except ImportError: - apt_install(['python3-pkg-resources']) - from pkg_resources import parse_version - - try: - installed_version = parse_version( - check_output(['/usr/local/sbin/charm-env', - '--version']).decode('utf8')) - except (CalledProcessError, FileNotFoundError): - installed_version = parse_version('0.0.0') - try: - bundled_version = parse_version( - check_output(['bin/charm-env', - '--version']).decode('utf8')) - except (CalledProcessError, FileNotFoundError): - bundled_version = parse_version('0.0.0') - if installed_version < bundled_version: - shutil.copy2('bin/charm-env', '/usr/local/sbin/') - - -def activate_venv(): - """ - Activate the venv if enabled in ``layer.yaml``. - - This is handled automatically for normal hooks, but actions might - need to invoke this manually, using something like: - - # Load modules from $JUJU_CHARM_DIR/lib - import sys - sys.path.append('lib') - - from charms.layer.basic import activate_venv - activate_venv() - - This will ensure that modules installed in the charm's - virtual environment are available to the action. - """ - from charms.layer import options - venv = os.path.abspath('../.venv') - vbin = os.path.join(venv, 'bin') - vpy = os.path.join(vbin, 'python') - use_venv = options.get('basic', 'use_venv') - if use_venv and '.venv' not in sys.executable: - # activate the venv - os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']]) - reload_interpreter(vpy) - layer.patch_options_interface() - layer.import_layer_libs() - - -def reload_interpreter(python): - """ - Reload the python interpreter to ensure that all deps are available. - - Newly installed modules in namespace packages sometimes seemt to - not be picked up by Python 3. - """ - os.execve(python, [python] + list(sys.argv), os.environ) - - -def apt_install(packages): - """ - Install apt packages. - - This ensures a consistent set of options that are often missed but - should really be set. - """ - if isinstance(packages, (str, bytes)): - packages = [packages] - - env = _get_subprocess_env() - - if 'DEBIAN_FRONTEND' not in env: - env['DEBIAN_FRONTEND'] = 'noninteractive' - - cmd = ['apt-get', - '--option=Dpkg::Options::=--force-confold', - '--assume-yes', - 'install'] - for attempt in range(3): - try: - check_call(cmd + packages, env=env) - except CalledProcessError: - if attempt == 2: # third attempt - raise - try: - # sometimes apt-get update needs to be run - check_call(['apt-get', 'update'], env=env) - except CalledProcessError: - # sometimes it's a dpkg lock issue - pass - sleep(5) - else: - break - - -def yum_install(packages): - """ Installs packages with yum. - This function largely mimics the apt_install function for consistency. - """ - if packages: - env = os.environ.copy() - cmd = ['yum', '-y', 'install'] - for attempt in range(3): - try: - check_call(cmd + packages, env=env) - except CalledProcessError: - if attempt == 2: - raise - try: - check_call(['yum', 'update'], env=env) - except CalledProcessError: - pass - sleep(5) - else: - break - else: - pass - - -def init_config_states(): - import yaml - from charmhelpers.core import hookenv - from charms.reactive import set_state - from charms.reactive import toggle_state - config = hookenv.config() - config_defaults = {} - config_defs = {} - config_yaml = os.path.join(hookenv.charm_dir(), 'config.yaml') - if os.path.exists(config_yaml): - with open(config_yaml) as fp: - config_defs = yaml.safe_load(fp).get('options', {}) - config_defaults = {key: value.get('default') - for key, value in config_defs.items()} - for opt in config_defs.keys(): - if config.changed(opt): - set_state('config.changed') - set_state('config.changed.{}'.format(opt)) - toggle_state('config.set.{}'.format(opt), config.get(opt)) - toggle_state('config.default.{}'.format(opt), - config.get(opt) == config_defaults[opt]) - - -def clear_config_states(): - from charmhelpers.core import hookenv, unitdata - from charms.reactive import remove_state - config = hookenv.config() - remove_state('config.changed') - for opt in config.keys(): - remove_state('config.changed.{}'.format(opt)) - remove_state('config.set.{}'.format(opt)) - remove_state('config.default.{}'.format(opt)) - unitdata.kv().flush() diff --git a/kata/lib/charms/layer/execd.py b/kata/lib/charms/layer/execd.py deleted file mode 100644 index 438d9a1..0000000 --- a/kata/lib/charms/layer/execd.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright 2014-2016 Canonical Limited. -# -# This file is part of layer-basic, the reactive base layer for Juju. -# -# charm-helpers is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License version 3 as -# published by the Free Software Foundation. -# -# charm-helpers is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with charm-helpers. If not, see . - -# This module may only import from the Python standard library. -import os -import sys -import subprocess -import time - -''' -execd/preinstall - -Read the layer-basic docs for more info on how to use this feature. -https://charmsreactive.readthedocs.io/en/latest/layer-basic.html#exec-d-support -''' - - -def default_execd_dir(): - return os.path.join(os.environ['JUJU_CHARM_DIR'], 'exec.d') - - -def execd_module_paths(execd_dir=None): - """Generate a list of full paths to modules within execd_dir.""" - if not execd_dir: - execd_dir = default_execd_dir() - - if not os.path.exists(execd_dir): - return - - for subpath in os.listdir(execd_dir): - module = os.path.join(execd_dir, subpath) - if os.path.isdir(module): - yield module - - -def execd_submodule_paths(command, execd_dir=None): - """Generate a list of full paths to the specified command within exec_dir. - """ - for module_path in execd_module_paths(execd_dir): - path = os.path.join(module_path, command) - if os.access(path, os.X_OK) and os.path.isfile(path): - yield path - - -def execd_sentinel_path(submodule_path): - module_path = os.path.dirname(submodule_path) - execd_path = os.path.dirname(module_path) - module_name = os.path.basename(module_path) - submodule_name = os.path.basename(submodule_path) - return os.path.join(execd_path, - '.{}_{}.done'.format(module_name, submodule_name)) - - -def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None): - """Run command for each module within execd_dir which defines it.""" - if stderr is None: - stderr = sys.stdout - for submodule_path in execd_submodule_paths(command, execd_dir): - # Only run each execd once. We cannot simply run them in the - # install hook, as potentially storage hooks are run before that. - # We cannot rely on them being idempotent. - sentinel = execd_sentinel_path(submodule_path) - if os.path.exists(sentinel): - continue - - try: - subprocess.check_call([submodule_path], stderr=stderr, - universal_newlines=True) - with open(sentinel, 'w') as f: - f.write('{} ran successfully {}\n'.format(submodule_path, - time.ctime())) - f.write('Removing this file will cause it to be run again\n') - except subprocess.CalledProcessError as e: - # Logs get the details. We can't use juju-log, as the - # output may be substantial and exceed command line - # length limits. - print("ERROR ({}) running {}".format(e.returncode, e.cmd), - file=stderr) - print("STDOUT<" -"description": | - Kata Containers is an open source community working to build a secure - container runtime with lightweight virtual machines that feel and perform - like containers, but provide stronger workload isolation using hardware - virtualization technology as a second layer of defense. -"tags": -- "misc" -- "containers" -"series": -- "focal" -- "bionic" -"requires": - "containerd": - "interface": "container-runtime" - "scope": "container" - "untrusted": - "interface": "untrusted-container-runtime" - "scope": "container" -"resources": - "kata-archive": - "type": "file" - "filename": "kata-archive.tar.gz" - "description": "Offline archive of kata" - -"subordinate": !!bool "true" diff --git a/kata/pydocmd.yml b/kata/pydocmd.yml deleted file mode 100644 index ab3b2ef..0000000 --- a/kata/pydocmd.yml +++ /dev/null @@ -1,16 +0,0 @@ -site_name: 'Status Management Layer' - -generate: - - status.md: - - charms.layer.status.WorkloadState - - charms.layer.status.maintenance - - charms.layer.status.maint - - charms.layer.status.blocked - - charms.layer.status.waiting - - charms.layer.status.active - - charms.layer.status.status_set - -pages: - - Status Management Layer: status.md - -gens_dir: docs diff --git a/kata/reactive/__init__.py b/kata/reactive/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/kata/reactive/kata.py b/kata/reactive/kata.py deleted file mode 100644 index e11c45f..0000000 --- a/kata/reactive/kata.py +++ /dev/null @@ -1,136 +0,0 @@ -import os -import requests - -from subprocess import ( - check_call, - check_output -) - -from charmhelpers.core import host - -from charms.reactive import ( - when, - when_not, - set_state, - remove_state, - endpoint_from_flag, - hook, -) - -from charmhelpers.fetch import ( - apt_install, - apt_update, - apt_purge, - apt_autoremove, - import_key -) - -from charmhelpers.core.hookenv import ( - resource_get -) - -from charms.layer import status - - -KATA_PACKAGES = [ - 'kata-runtime', - 'kata-proxy', - 'kata-shim' -] - - -@when_not('kata.installed') -@when_not('endpoint.untrusted.departed') -def install_kata(): - """ - Install the Kata container runtime. - - :returns: None - """ - dist = host.lsb_release() - release = '{}_{}'.format( - dist['DISTRIB_ID'], - dist['DISTRIB_RELEASE'] - ) - - arch = check_output(['arch']).decode().strip() - - archive = resource_get('kata-archive') - - if not archive or os.path.getsize(archive) == 0: - status.maintenance('Installing Kata via apt') - gpg_key = requests.get( - 'http://download.opensuse.org/repositories/home:/katacontainers:/' - 'releases:/{}:/master/x{}/Release.key'.format(arch, release)).text - import_key(gpg_key) - - with open('/etc/apt/sources.list.d/kata-containers.list', 'w') as f: - f.write( - 'deb http://download.opensuse.org/repositories/home:/' - 'katacontainers:/releases:/{}:/master/x{}/ /' - .format(arch, release) - ) - - apt_update() - apt_install(KATA_PACKAGES) - - else: - status.maintenance('Installing Kata via resource') - unpack = '/tmp/kata-debs' - - if not os.path.isdir(unpack): - os.makedirs(unpack, exist_ok=True) - - check_call(['tar', '-xvf', archive, '-C', unpack]) - check_call('apt-get install -y {}/*.deb'.format(unpack), shell=True) - - status.active('Kata runtime available') - set_state('kata.installed') - - -@when('endpoint.untrusted.departed') -def purge_kata(): - """ - Purge Kata containers. - - :return: None - """ - status.maintenance('Purging Kata') - - apt_purge(KATA_PACKAGES, fatal=False) - - source = '/etc/apt/sources.list.d/kata-containers.list' - if os.path.isfile(source): - os.remove(source) - - apt_autoremove() - - remove_state('kata.installed') - - -@when('kata.installed') -@when('endpoint.untrusted.joined') -@when_not('endpoint.untrusted.departed') -def publish_config(): - """ - Pass configuration over the interface. - - :return: None - """ - endpoint = endpoint_from_flag('endpoint.untrusted.joined') - endpoint.set_config( - name='kata', - binary_path='/usr/bin/kata-runtime' - ) - - -@hook('pre-series-upgrade') -def pre_series_upgrade(): - """Set status during series upgrade.""" - status.blocked('Series upgrade in progress') - - -@hook('post-series-upgrade') -def post_series_upgrade(): - """Reset status to active after series upgrade.""" - status.active('Kata runtime available') diff --git a/kata/reactive/status.py b/kata/reactive/status.py deleted file mode 100644 index 2f33f3f..0000000 --- a/kata/reactive/status.py +++ /dev/null @@ -1,4 +0,0 @@ -from charms import layer - - -layer.status._initialize() diff --git a/kata/requirements.txt b/kata/requirements.txt deleted file mode 100644 index 55543d9..0000000 --- a/kata/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -mock -flake8 -pytest diff --git a/kata/revision b/kata/revision deleted file mode 100644 index c227083..0000000 --- a/kata/revision +++ /dev/null @@ -1 +0,0 @@ -0 \ No newline at end of file diff --git a/kata/tests/conftest.py b/kata/tests/conftest.py deleted file mode 100644 index a92e249..0000000 --- a/kata/tests/conftest.py +++ /dev/null @@ -1,4 +0,0 @@ -import charms.unit_test - - -charms.unit_test.patch_reactive() diff --git a/kata/tests/test_kata_reactive.py b/kata/tests/test_kata_reactive.py deleted file mode 100644 index 421b0dd..0000000 --- a/kata/tests/test_kata_reactive.py +++ /dev/null @@ -1,35 +0,0 @@ -from reactive import kata - - -def test_packages_list(): - """Assert KATA_PACKAGES is a list of strings.""" - assert isinstance(kata.KATA_PACKAGES, list) - for item in kata.KATA_PACKAGES: - assert isinstance(item, str) - - -def test_install_kata(): - """Assert install_kata is a method.""" - assert callable(kata.install_kata) - - -def test_purge_kata(): - """Assert purge_kata is a method.""" - assert callable(kata.purge_kata) - - -def test_publist_config(): - """Assert publish_config is a method.""" - assert callable(kata.publish_config) - - -def test_series_upgrade(): - """Assert status is set during series upgrade.""" - assert kata.status.blocked.call_count == 0 - assert kata.status.active.call_count == 0 - kata.pre_series_upgrade() - assert kata.status.blocked.call_count == 1 - assert kata.status.active.call_count == 0 - kata.post_series_upgrade() - assert kata.status.blocked.call_count == 1 - assert kata.status.active.call_count == 1 diff --git a/kata/tox.ini b/kata/tox.ini deleted file mode 100644 index 0c9eaec..0000000 --- a/kata/tox.ini +++ /dev/null @@ -1,34 +0,0 @@ -[flake8] -max-line-length = 120 -ignore = D100 - -[tox] -skipsdist = True -envlist = lint,py3 - -[tox:travis] -3.5: lint,py3 -3.6: lint, py3 -3.7: lint, py3 -3.8: lint, py3 - -[testenv] -basepython = python3 -setenv = - PYTHONPATH={toxinidir} -deps = - pyyaml - pytest - pytest-cov - flake8 - flake8-docstrings - requests - git+https://github.com/juju-solutions/charms.unit_test/#egg=charms.unit_test -commands = - pytest --cov-report term-missing \ - --cov reactive.kata --cov-fail-under 30 \ - --tb native -s {posargs} - -[testenv:lint] -envdir = {toxworkdir}/py3 -commands = flake8 {toxinidir}/reactive {toxinidir}/tests diff --git a/kata/version b/kata/version deleted file mode 100644 index 91808cc..0000000 --- a/kata/version +++ /dev/null @@ -1 +0,0 @@ -0ea81f0c \ No newline at end of file diff --git a/kata/wheelhouse.txt b/kata/wheelhouse.txt deleted file mode 100644 index 4413da7..0000000 --- a/kata/wheelhouse.txt +++ /dev/null @@ -1,21 +0,0 @@ -# layer:basic -# pip is pinned to <19.0 to avoid https://github.com/pypa/pip/issues/6164 -# even with installing setuptools before upgrading pip ends up with pip seeing -# the older setuptools at the system level if include_system_packages is true -pip>=18.1,<19.0 -# pin Jinja2, PyYAML and MarkupSafe to the last versions supporting python 3.5 -# for trusty -Jinja2<=2.10.1 -PyYAML<=5.2 -MarkupSafe<2.0.0 -setuptools<42 -setuptools-scm<=1.17.0 -charmhelpers>=0.4.0,<1.0.0 -charms.reactive>=0.1.0,<2.0.0 -wheel<0.34 -# pin netaddr to avoid pulling importlib-resources -netaddr<=0.7.19 - -# kata -requests - diff --git a/kata/wheelhouse/Jinja2-2.10.1.tar.gz b/kata/wheelhouse/Jinja2-2.10.1.tar.gz deleted file mode 100644 index ffd1054..0000000 Binary files a/kata/wheelhouse/Jinja2-2.10.1.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/MarkupSafe-1.1.1.tar.gz b/kata/wheelhouse/MarkupSafe-1.1.1.tar.gz deleted file mode 100644 index a6dad8e..0000000 Binary files a/kata/wheelhouse/MarkupSafe-1.1.1.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/PyYAML-5.2.tar.gz b/kata/wheelhouse/PyYAML-5.2.tar.gz deleted file mode 100644 index 666d12a..0000000 Binary files a/kata/wheelhouse/PyYAML-5.2.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/Tempita-0.5.2.tar.gz b/kata/wheelhouse/Tempita-0.5.2.tar.gz deleted file mode 100644 index 755befc..0000000 Binary files a/kata/wheelhouse/Tempita-0.5.2.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/certifi-2021.5.30.tar.gz b/kata/wheelhouse/certifi-2021.5.30.tar.gz deleted file mode 100644 index 4eb2a5f..0000000 Binary files a/kata/wheelhouse/certifi-2021.5.30.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/charmhelpers-0.20.22.tar.gz b/kata/wheelhouse/charmhelpers-0.20.22.tar.gz deleted file mode 100644 index bd5d222..0000000 Binary files a/kata/wheelhouse/charmhelpers-0.20.22.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/charms.reactive-1.4.1.tar.gz b/kata/wheelhouse/charms.reactive-1.4.1.tar.gz deleted file mode 100644 index 03bc1fe..0000000 Binary files a/kata/wheelhouse/charms.reactive-1.4.1.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/charset-normalizer-2.0.3.tar.gz b/kata/wheelhouse/charset-normalizer-2.0.3.tar.gz deleted file mode 100644 index 804b4cc..0000000 Binary files a/kata/wheelhouse/charset-normalizer-2.0.3.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/idna-3.2.tar.gz b/kata/wheelhouse/idna-3.2.tar.gz deleted file mode 100644 index 6febfb2..0000000 Binary files a/kata/wheelhouse/idna-3.2.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/netaddr-0.7.19.tar.gz b/kata/wheelhouse/netaddr-0.7.19.tar.gz deleted file mode 100644 index cc31d9d..0000000 Binary files a/kata/wheelhouse/netaddr-0.7.19.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/pbr-5.6.0.tar.gz b/kata/wheelhouse/pbr-5.6.0.tar.gz deleted file mode 100644 index 0d5c965..0000000 Binary files a/kata/wheelhouse/pbr-5.6.0.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/pip-18.1.tar.gz b/kata/wheelhouse/pip-18.1.tar.gz deleted file mode 100644 index a18192d..0000000 Binary files a/kata/wheelhouse/pip-18.1.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/pyaml-20.4.0.tar.gz b/kata/wheelhouse/pyaml-20.4.0.tar.gz deleted file mode 100644 index 0d5fd76..0000000 Binary files a/kata/wheelhouse/pyaml-20.4.0.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/requests-2.26.0.tar.gz b/kata/wheelhouse/requests-2.26.0.tar.gz deleted file mode 100644 index 101dc79..0000000 Binary files a/kata/wheelhouse/requests-2.26.0.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/setuptools-41.6.0.zip b/kata/wheelhouse/setuptools-41.6.0.zip deleted file mode 100644 index 3345759..0000000 Binary files a/kata/wheelhouse/setuptools-41.6.0.zip and /dev/null differ diff --git a/kata/wheelhouse/setuptools_scm-1.17.0.tar.gz b/kata/wheelhouse/setuptools_scm-1.17.0.tar.gz deleted file mode 100644 index 43b16c7..0000000 Binary files a/kata/wheelhouse/setuptools_scm-1.17.0.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/six-1.16.0.tar.gz b/kata/wheelhouse/six-1.16.0.tar.gz deleted file mode 100644 index 5bf3a27..0000000 Binary files a/kata/wheelhouse/six-1.16.0.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/urllib3-1.26.6.tar.gz b/kata/wheelhouse/urllib3-1.26.6.tar.gz deleted file mode 100644 index 5b90623..0000000 Binary files a/kata/wheelhouse/urllib3-1.26.6.tar.gz and /dev/null differ diff --git a/kata/wheelhouse/wheel-0.33.6.tar.gz b/kata/wheelhouse/wheel-0.33.6.tar.gz deleted file mode 100644 index c922c4e..0000000 Binary files a/kata/wheelhouse/wheel-0.33.6.tar.gz and /dev/null differ