├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── kubectl.myna.sh ├── kubefuse ├── __init__.py ├── cache.py ├── client.py ├── filesystem.py ├── kubefuse.py └── path.py ├── logo.png ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── kubectl.json ├── test_cache.py ├── test_client.py ├── test_filesystem.py └── test_path.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | kubectl.db 4 | build/ 5 | dist/ 6 | kubefuse.egg-info/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | - '3.5' 5 | 6 | before_install: 7 | - sudo apt-get -qq update 8 | - sudo apt-get install -y libfuse-dev 9 | 10 | install: 11 | - pip install -r requirements.txt 12 | 13 | script: 14 | - wget https://github.com/SpectoLabs/myna/releases/download/v1/myna 15 | - chmod +x myna 16 | - export PATH=$PWD:$PATH 17 | - make test 18 | 19 | notifications: 20 | slack: 21 | secure: rDzLrNVdqxnPLQqWsC4511edbsjD3pLyK41Py8xfUUTU+hTadnzgWI31kiO7V+6t/lnK8TnI54l8vDYuFhqDPE/F11LDi6NpLREjubIp1rY1L0vtZJLuHN+za/R9BpajWsJOEYsQCUUAdHwh0YCXoJ7nWTWblMVy+IyFDFSPky0ezeXpqVw+HSSbLE7rS7YOi7gQjM6n5QVMYnLMevdZD9NYmi7xvFaAff2qmenRysNPLiJP08Z3alFkwo/f1WmJbVtsUw6yh9JkCAr9xg6e2VGWwx0XAhWVYp+rS573WKnz0Jd13HKzwGu2ChW3AJphYRzwFVVLKve8GSSBOSFnVAveUmUaitmegC57hOKrday1QUsv6VLVzMdlvzOkWt5nR0lDXgED8yI9sW6Ib7YFbIXaLnt03+iI+nP9wHUkAqx2ZFrUeeEDJmoU69D0UuULIo4cubIoZZMUVwFUOhIEjDtTzzDWzR7wFZ8aIGiCCY+kbZIylWkj30Q3sbBcdWhgMo29pz98rt75B6y2FJ6LTjyfDczL4GIyP0D9gtMXg2mjRCmjNCp2wS3YQeAjQ9JfmVx0SdztLpj72iRqqp0ugDhktRf1cs6vtU0gtuvGz1xmazz7q8erLNypnB7hpA4gTbyETnsL3EaxgenqVwim3qZtEETAiLv1r1OOj5+hsu8= 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015 SpectoLabs Ltd. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | myna: 2 | ./kubectl.myna.sh 3 | 4 | import_myna: 5 | DATABASE_LOCATION="`pwd`/kubectl.db" myna --import tests/kubectl.json 6 | 7 | test: import_myna 8 | DATABASE_LOCATION="`pwd`/kubectl.db" nosetests -s 9 | 10 | test3: import_myna 11 | DATABASE_LOCATION="`pwd`/kubectl.db" python3 -m nose -s 12 | 13 | release: 14 | python setup.py register 15 | python setup.py sdist upload 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | KubeFuse 2 | ======== 3 | 4 | ![KubeFuse](logo.png) 5 | 6 | Kubernetes as a Filesystem [![Build Status](https://travis-ci.org/opencredo/kubefuse.svg?branch=master)](https://travis-ci.org/opencredo/kubefuse) 7 | 8 | 9 | ## Why? 10 | 11 | Because kubectl is great, but sometimes a bit slow to navigate. 12 | 13 | Enter KubeFuse. 14 | 15 | Beta quality software for quick Kubernetes browsing and editing. What's not to love. 16 | 17 | ## Features 18 | 19 | * Browse Kubernetes resources in your file system... 20 | * ...with (some of) your favourite tools: `ls`, `find`, `cat`, `vim`, ... 21 | * List all your favourite resources such as: services, replication controllers, pods and namespaces. All entity types up to v1.3 are supported. 22 | * Access resource descriptions as files (eg. `cat ~/kubernetes/default/pod/postgres-aazm1/describe`) 23 | * Quickly read resources as YAML or JSON (eg. `cat ~/kubernetes/default/pod/postgres-aazm1/json`) 24 | * Edit resources with your editor of choice and have Kubernetes update on writes (`vim ~/kubernetes/default/rc/postgres/json` :raising_hand:) 25 | * Works with Python 2 and 3 26 | 27 | A more detailed introductory post can be found on [this beautiful blog](https://opencredo.com/introducing-kubefuse-file-system-kubernetes/). 28 | 29 | ## Requirements 30 | 31 | KubeFuse runs on both Linux and Mac, but does require additional libraries to be installed (eg. OSXFUSE). 32 | 33 | KubeFuse also uses the kubectl binary under the hood so this needs to be on the path. 34 | 35 | ## Setup and Usage 36 | 37 | ### Getting the latest release 38 | 39 | You should be able to: 40 | 41 | ``` 42 | pip install kubefuse 43 | ``` 44 | 45 | After which the kubefuse command will be installed into a `bin/` directory that 46 | is hopefully already on your path (if not look for the line starting with 47 | `Installing kubefuse script to ....` and add that directory to your PATH and 48 | restart your shell). 49 | 50 | You should then be able to run: 51 | 52 | ``` 53 | kubefuse [MOUNTPOINT] 54 | ``` 55 | 56 | ### From Source 57 | 58 | When "building" from source: 59 | 60 | ``` 61 | pip install -r requirements.txt 62 | ``` 63 | 64 | Will install all the dependencies (on fresh systems you may need to 65 | `easy_install pip` first). After which you can run KubeFuse with: 66 | 67 | ``` 68 | python kubefuse/kubefuse.py [MOUNTPOINT] 69 | ``` 70 | 71 | 72 | ## Tests 73 | 74 | KubeFuse is extensively tested using a tool called 75 | [Myna](https://github.com/SpectoLabs/myna). It also uses the `nose` framework 76 | to discover and orchestrate the tests. To run the tests install Myna and then: 77 | 78 | ``` 79 | make test 80 | ``` 81 | 82 | Examples 83 | ======== 84 | 85 | Create the mount: 86 | 87 | ``` 88 | python kubefuse.py ~/kubernetes 89 | ``` 90 | 91 | List all pods in the default namespace: 92 | 93 | ``` 94 | ls ~/kubernetes/default/pod/ 95 | ``` 96 | 97 | List all known objects in the default namespace: 98 | 99 | ``` 100 | find ~/kubernetes/default -type d -mindepth 2 101 | ``` 102 | 103 | Describe the `postgres` pod: 104 | 105 | ``` 106 | cat ~/kubernetes/default/pod/postgres-aazm1/describe 107 | ``` 108 | 109 | Get logs from a `graphite` pod: 110 | 111 | ``` 112 | cat ~/kubernetes/default/pod/graphite-i3bb2/logs 113 | ``` 114 | 115 | Export the `postgres` replication controller to YAML: 116 | 117 | ``` 118 | cat ~/kubernetes/default/rc/postgres/yaml 119 | ``` 120 | 121 | Export the `postgres` replication controller to JSON: 122 | 123 | ``` 124 | cat ~/kubernetes/default/rc/postgres/json 125 | ``` 126 | 127 | Export all service definitions in the default namespace: 128 | 129 | ``` 130 | find ~/kubernetes/default/svc -name yaml | while read line ; do cat $line ; echo "----" ; echo ; done 131 | ``` 132 | 133 | We can edit replication controllers and other resources in our editor. Saving the file will replace the resource in kubernetes: 134 | 135 | ``` 136 | vim ~/kubernetes/default/rc/postgres/json 137 | ``` 138 | 139 | ## License 140 | 141 | Apache License version 2.0 [See LICENSE for details](./blob/master/LICENSE). 142 | 143 | (c) [OpenCredo](https://opencredo.com) 2016. 144 | 145 | -------------------------------------------------------------------------------- /kubectl.myna.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Put myna into capture mode 4 | export CAPTURE=1 5 | 6 | # Remove the old myna database if it exists 7 | rm -f processes.db 8 | 9 | # Run the commands I need for testing: 10 | myna kubectl get namespaces 11 | myna kubectl get ns -o yaml 12 | myna kubectl get pods --all-namespaces 13 | myna kubectl get pod -o yaml --namespace default 14 | myna kubectl get svc --all-namespaces 15 | myna kubectl get svc -o yaml --namespace default 16 | myna kubectl get rc --all-namespaces 17 | myna kubectl get rc -o yaml --namespace default 18 | 19 | # I don't want to hardcode pod names, so I'm getting the first one 20 | # and using that. The python unit tests do the same thing. 21 | 22 | FIRST_POD=$(kubectl get pod -o yaml | grep "name: " | head -1 | awk '{print $2}') 23 | myna kubectl get pod $FIRST_POD -o yaml --namespace default 24 | myna kubectl get pod $FIRST_POD -o json --namespace default 25 | myna kubectl describe pod $FIRST_POD --namespace default 26 | myna kubectl logs $FIRST_POD --namespace default 27 | 28 | # Export the results 29 | myna --export | python -m json.tool > tests/kubectl.json 30 | rm -f processes.db 31 | -------------------------------------------------------------------------------- /kubefuse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencredo/kubefuse/334b540a68dec48f9d7d93bfb5a8b89681d2edbd/kubefuse/__init__.py -------------------------------------------------------------------------------- /kubefuse/cache.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class ExpiringCache(object): 8 | def __init__(self, expire_in_seconds): 9 | self._cache = {} 10 | self._timestamps = {} 11 | self.EXPIRE_IN_SECONDS = expire_in_seconds 12 | 13 | def set(self, key, value): 14 | t = time.time() + self.EXPIRE_IN_SECONDS 15 | self._cache[key] = value 16 | self._timestamps[key] = t 17 | logger.info("Add key '%s' to cache (expires %d)" % (key, t)) 18 | 19 | def get(self, key): 20 | if key not in self._timestamps: 21 | return None 22 | now = time.time() 23 | expires_at = self._timestamps[key] 24 | if now < expires_at: 25 | logger.info("Retrieved '%s' from cache" % key) 26 | return self._cache[key] 27 | self.delete(key) 28 | return None 29 | 30 | def delete(self, key): 31 | del(self._timestamps[key]) 32 | del(self._cache[key]) 33 | -------------------------------------------------------------------------------- /kubefuse/client.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import yaml 3 | import tempfile 4 | import six 5 | 6 | from . import cache 7 | 8 | 9 | class KubernetesClient(object): 10 | def __init__(self, kubeconfig=None, cluster=None, context=None, user=None): 11 | self._cache = cache.ExpiringCache(30) 12 | self.kubeconfig = kubeconfig 13 | self.cluster = cluster 14 | self.context = context 15 | self.user = user 16 | 17 | def _base_kubectl_command(self): 18 | result = ['kubectl'] 19 | if self.kubeconfig is not None: 20 | result += ['--kubeconfig', self.kubeconfig] 21 | if self.cluster is not None: 22 | result += ['--cluster', self.cluster] 23 | if self.context is not None: 24 | result += ['--context', self.context] 25 | if self.user is not None: 26 | result += ['--user', self.user] 27 | return result 28 | 29 | def _run_command(self, cmd): 30 | return subprocess.check_output(' '.join(self._base_kubectl_command() + cmd), shell=True) 31 | 32 | def _run_command_and_parse_yaml(self, cmd): 33 | return yaml.load(self._run_command(cmd)) 34 | 35 | @staticmethod 36 | def _namespace(ns): 37 | return ['--namespace', ns] if ns != 'all' else ['--all-namespaces'] 38 | 39 | def _load_from_cache_or_do(self, key, func): 40 | cached = self._cache.get(key) 41 | if cached is not None: 42 | return cached 43 | result = func() 44 | self._cache.set(key, result) 45 | return result 46 | 47 | def get_namespaces(self): 48 | key = "namespaces" 49 | cb = self._get_namespaces 50 | return self._load_from_cache_or_do(key, cb) 51 | 52 | def get_entities(self, ns, entity): 53 | key = "%s.%s" % (ns, entity) 54 | cb = lambda: self._get_entities(ns, entity) 55 | return self._load_from_cache_or_do(key, cb) 56 | 57 | def get_object_in_format(self, ns, entity_type, object, format): 58 | key = "%s.%s.%s.%s" % (ns, entity_type, object, format) 59 | cb = lambda: self._get_object_in_format(ns, entity_type, object, format) 60 | return self._load_from_cache_or_do(key, cb) 61 | 62 | def describe(self, ns, entity_type, object): 63 | key = "%s.%s.%s.describe" % (ns, entity_type, object) 64 | cb = lambda: self._describe(ns, entity_type, object) 65 | return self._load_from_cache_or_do(key, cb) 66 | 67 | def delete_from_cache(self, ns, entity_type, object, format): 68 | key = "%s.%s.%s.%s" % (ns, entity_type, object, format) 69 | self._cache.delete(key) 70 | 71 | def logs(self, ns, object): 72 | key = "%s.pod.%s.logs" % (ns, object) 73 | cb = lambda: self._logs(ns, object) 74 | return self._load_from_cache_or_do(key, cb) 75 | 76 | def get_pods(self, ns='default'): 77 | return self.get_entities(ns, 'pod') 78 | def get_services(self, ns='default'): 79 | return self.get_entities(ns, 'svc') 80 | def get_replication_controllers(self, ns='default'): 81 | return self.get_entities(ns, 'rc') 82 | 83 | def replace(self, data): 84 | tmpfile = tempfile.mktemp() 85 | with open(tmpfile, 'w') as f: 86 | f.write(data) 87 | six.print_(self._run_command(('apply -f ' + tmpfile).split())) 88 | 89 | 90 | def _get_namespaces(self): 91 | payload = self._run_command_and_parse_yaml('get ns -o yaml'.split()) 92 | if payload is None or 'items' not in payload: 93 | return [] 94 | return [item['metadata']['name'] for item in payload['items']] 95 | 96 | def _get_entities(self, ns, entity): 97 | payload = self._run_command_and_parse_yaml(['get', entity, '-o', 'yaml'] + self._namespace(ns)) 98 | if payload is None or 'items' not in payload: 99 | return [] 100 | return [item['metadata']['name'] for item in payload['items']] 101 | 102 | def _get_object_in_format(self, ns, entity_type, object, format): 103 | return self._run_command(['get', entity_type, object, '-o', format] + self._namespace(ns)) 104 | 105 | def _describe(self, ns, entity_type, object): 106 | return self._run_command(['describe', entity_type, object] + self._namespace(ns)) 107 | 108 | def _logs(self, ns, object): 109 | return self._run_command(['logs', object] + self._namespace(ns)) 110 | 111 | -------------------------------------------------------------------------------- /kubefuse/filesystem.py: -------------------------------------------------------------------------------- 1 | from stat import S_IFDIR, S_IFREG 2 | from time import time 3 | import logging 4 | import errno 5 | from fuse import FuseOSError 6 | from . import path 7 | KubePath = path.KubePath 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class KubeFileSystem(object): 13 | def __init__(self, client): 14 | self.client = client 15 | self.open_files = {} 16 | self.flushed = {} 17 | 18 | @staticmethod 19 | def _stat_dir(): 20 | return dict(st_mode=(S_IFDIR | 0o555), st_nlink=2, 21 | st_size=0, st_ctime=time(), st_mtime=time(), 22 | st_atime=time()) 23 | 24 | @staticmethod 25 | def _stat_file(client, path, size=None): 26 | if size is None: 27 | size = len(path.do_action(client)) 28 | ts = path.get_creation_date_for_action_file(client) 29 | if ts is None: 30 | ts = time() 31 | return dict(st_mode=(S_IFREG | path.get_mode()), st_nlink=1, 32 | st_size=size, st_ctime=ts, st_mtime=ts, 33 | st_atime=ts) 34 | 35 | def open_for_writing(self, path): 36 | if path not in self.open_files: 37 | contents = KubePath().parse_path(path).do_action(self.client) 38 | self.open_files[path] = bytes(contents) 39 | 40 | def open(self, path, fh): 41 | pass 42 | 43 | def truncate(self, path, length): 44 | self.open_for_writing(path) 45 | self.open_files[path] = self.open_files[path][:length] 46 | 47 | def write(self, path, buf, offset): 48 | self.open_for_writing(path) 49 | if type(buf) == str: 50 | buf = buf.encode('utf-8') 51 | self.open_files[path] = self.open_files[path][:offset] + buf 52 | return len(buf) 53 | 54 | def close(self, path): 55 | if path not in self.open_files: 56 | return 57 | self.persist(path, self.open_files[path]) 58 | del(self.open_files[path]) 59 | del(self.flushed[path]) 60 | p = KubePath().parse_path(path) 61 | self.client.delete_from_cache(p.namespace, p.resource_type, 62 | p.object_id, p.action) 63 | 64 | def sync(self, path, dry_run=False): 65 | if path not in self.open_files: 66 | return 67 | self.persist(path, self.open_files[path], dry_run) 68 | 69 | def persist(self, path, data, dry_run=False): 70 | if path in self.flushed and data == self.flushed[path]: 71 | return 72 | self.flushed[path] = data 73 | if not dry_run: 74 | self.client.replace(data) 75 | 76 | def list_files(self, path): 77 | p = KubePath().parse_path(path) 78 | if not p.exists(self.client): 79 | logger.info("path doesn't exist") 80 | raise FuseOSError(errno.ENOENT) 81 | if not p.is_dir(): 82 | logger.info("not a directory") 83 | raise FuseOSError(errno.ENOTDIR) 84 | if p.object_id is not None: 85 | if p.resource_type != 'pod': 86 | return p.SUPPORTED_ACTIONS 87 | else: 88 | return p.SUPPORTED_POD_ACTIONS 89 | if p.resource_type is not None: 90 | return self.client.get_entities(p.namespace, p.resource_type) 91 | if p.namespace is not None: 92 | return p.SUPPORTED_RESOURCE_TYPES 93 | return self.client.get_namespaces() # + ['all'] 94 | 95 | def getattr(self, path): 96 | p = KubePath().parse_path(path) 97 | if path in self.open_files: 98 | return self._stat_file(self.client, p, len(self.open_files[path])) 99 | if not p.exists(self.client): 100 | logger.info("path '%s' doesn't exist" % path) 101 | raise FuseOSError(errno.ENOENT) 102 | if p.action is not None: 103 | return self._stat_file(self.client, p) 104 | return self._stat_dir() 105 | 106 | def read(self, path, size, offset): 107 | p = KubePath().parse_path(path) 108 | if not p.is_file(): 109 | raise FuseOSError(errno.ENOENT) 110 | if path in self.flushed: 111 | data = self.flushed[path] 112 | else: 113 | data = p.do_action(self.client) 114 | return data[offset:offset + size] 115 | 116 | -------------------------------------------------------------------------------- /kubefuse/kubefuse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import logging 4 | import six 5 | import argparse 6 | import sys 7 | try: 8 | from fuse import FUSE, FuseOSError, Operations, LoggingMixIn 9 | except EnvironmentError: 10 | print("It looks like the Fuse system library is missing.") 11 | print("Please install libfuse using your OS's package manager, or download OSXFUSE if you're on a Mac") 12 | sys.exit(1) 13 | 14 | 15 | from . import client 16 | from . import filesystem 17 | 18 | 19 | class KubeFuse(LoggingMixIn, Operations): 20 | 21 | def __init__(self, mount, kubeconfig=None, cluster=None, context=None, user=None): 22 | self.client = client.KubernetesClient(kubeconfig, cluster, context, user) 23 | self.fs = filesystem.KubeFileSystem(self.client) 24 | self.fd = 0 25 | six.print_("Mounting KubeFuse on", mount) 26 | 27 | def readdir(self, path, fh): 28 | return self.fs.list_files(path) 29 | 30 | def getattr(self, path, fh=None): 31 | return self.fs.getattr(path) 32 | 33 | def open(self, path, fh): 34 | self.fd += 1 35 | self.fs.open(path, fh) 36 | return self.fd 37 | 38 | def read(self, path, size, offset, fh): 39 | return self.fs.read(path, size, offset) 40 | 41 | def truncate(self, path, length, fh=None): 42 | self.fs.truncate(path, length) 43 | return 0 44 | 45 | def write(self, path, buf, offset, fh): 46 | written = self.fs.write(path, buf, offset) 47 | return written 48 | 49 | def flush(self, path, fh): 50 | self.fs.sync(path) 51 | return 0 52 | 53 | def release(self, path, fh): 54 | self.fs.close(path) 55 | return 0 56 | 57 | def parse_args(): 58 | parser = argparse.ArgumentParser(description='A file system view for Kubernetes') 59 | parser.add_argument('mountpoint', metavar='MOUNTPOINT', type=str, 60 | help='The directory to mount on') 61 | parser.add_argument('--kubeconfig', dest='kubeconfig', 62 | help='Path to the kubeconfig file') 63 | parser.add_argument('--cluster', dest='cluster', 64 | help='The name of the kubeconfig cluster to use') 65 | parser.add_argument('--context', dest='context', 66 | help='The name of the kubeconfig context to use') 67 | parser.add_argument('--user', dest='user', 68 | help='The name of the kubeconfig user to use') 69 | parser.add_argument('--verbose', '-v', action='count', dest='verbosity', 70 | default=0, help='Verbosity of program output') 71 | return parser.parse_args() 72 | 73 | def main(): 74 | args = parse_args() 75 | if args.verbosity == 0: 76 | logging.basicConfig(level=logging.WARNING) 77 | elif args.verbosity == 1: 78 | logging.basicConfig(level=logging.INFO) 79 | elif args.verbosity == 2: 80 | logging.basicConfig(level=logging.DEBUG) 81 | else: 82 | logging.basicConfig(level=0) 83 | FUSE(KubeFuse(args.mountpoint, args.kubeconfig, args.cluster, args.context), 84 | args.mountpoint, foreground=True) 85 | 86 | if __name__ == '__main__': 87 | main() 88 | -------------------------------------------------------------------------------- /kubefuse/path.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | 4 | 5 | class KubePath(object): 6 | def __init__(self, namespace=None, resource_type=None, object_id=None, action=None): 7 | self.namespace = namespace 8 | self.resource_type = resource_type 9 | self.object_id = object_id 10 | self.action = None 11 | self.SUPPORTED_RESOURCE_TYPES = [ 12 | 'configmaps', 13 | 'componentstatuses', 14 | 'daemonsets', 15 | 'deployments', 16 | 'endpoints', 17 | 'events', 18 | 'horizontalpodautoscalers', 19 | 'ingress', 20 | 'jobs', 21 | 'limits', 22 | 'nodes', 23 | 'pod', 24 | 'pv', 25 | 'pvc', 26 | 'quota', 27 | 'rc', 28 | 'replicasets', 29 | 'secrets', 30 | 'serviceaccounts', 31 | 'svc', 32 | ] 33 | self.SUPPORTED_ACTIONS = ['describe', 'json', 'yaml'] 34 | self.SUPPORTED_POD_ACTIONS = ['logs'] + self.SUPPORTED_ACTIONS 35 | 36 | def parse_path(self, path): 37 | if path == '/': 38 | return self 39 | parts = path[1:].split("/") 40 | self.namespace = parts[0] if len(parts) > 0 else None 41 | self.resource_type = parts[1] if len(parts) > 1 else None 42 | self.object_id = parts[2] if len(parts) > 2 else None 43 | self.action = parts[3] if len(parts) > 3 else None 44 | return self 45 | 46 | def get_creation_date_for_action_file(self, client): 47 | if self.action not in ['json', 'yaml', 'describe']: 48 | return None 49 | metadata = client.get_object_in_format( 50 | self.namespace, self.resource_type, 51 | self.object_id, 'json') 52 | metadata = "" if metadata is None else metadata 53 | json_data = json.loads(metadata.decode('utf-8')) 54 | ts = None 55 | if 'metadata' in json_data: 56 | if 'creationTimestamp' in json_data['metadata']: 57 | timestamp = json_data['metadata']['creationTimestamp'] 58 | if timestamp is not None: 59 | ts = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ") 60 | ts = int(ts.strftime("%s")) 61 | return ts 62 | 63 | def is_dir(self): 64 | return self.action is None 65 | 66 | def is_file(self): 67 | return not(self.is_dir()) 68 | 69 | def exists(self, client): 70 | if self.namespace is None: 71 | return True 72 | namespaces = client.get_namespaces() 73 | if self.namespace not in namespaces: 74 | return False 75 | if self.resource_type is None: 76 | return True 77 | if self.resource_type not in self.SUPPORTED_RESOURCE_TYPES: 78 | return False 79 | if self.object_id is None: 80 | return True 81 | entities = client.get_entities(self.namespace, self.resource_type) 82 | if self.object_id not in entities: 83 | return False 84 | if self.action is None: 85 | return True 86 | if self.resource_type == 'pod' and self.action in self.SUPPORTED_POD_ACTIONS: 87 | return True 88 | if self.action not in self.SUPPORTED_ACTIONS: 89 | return False 90 | return True 91 | 92 | def do_action(self, client): 93 | ns, rt, oid = self.namespace, self.resource_type, self.object_id 94 | if self.action == 'describe': 95 | return client.describe(ns, rt, oid) 96 | if self.action == 'logs': 97 | return client.logs(ns, oid) 98 | if self.action in ['json', 'yaml']: 99 | return client.get_object_in_format(ns, rt, oid, self.action) 100 | 101 | def get_mode(self): 102 | return 0o444 if self.action not in ['json', 'yaml'] else 0o666 103 | 104 | def __repr__(self): 105 | result = ['<'] 106 | if self.action is not None: 107 | result.append("action %s on" % self.action) 108 | if self.object_id is not None: 109 | result.append('object %s' % self.object_id) 110 | if self.resource_type is not None: 111 | result.append("of type %s" % self.resource_type) 112 | if self.namespace is not None: 113 | result.append('in namespace %s' % self.namespace) 114 | result.append('>') 115 | return " ".join(result) 116 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opencredo/kubefuse/334b540a68dec48f9d7d93bfb5a8b89681d2edbd/logo.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fusepy==2.0.4 2 | PyHamcrest==1.8.5 3 | python-myna==1.2.0 4 | PyYAML==3.11 5 | six==1.10.0 6 | nose 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name = "kubefuse", 5 | version = "0.7.3", 6 | packages = find_packages(), 7 | author = "Bart Spaans", 8 | author_email = "bart.spaans@gmail.com", 9 | url = "https://github.com/bspaans/kubefuse", 10 | description = "A FUSE file system for Kubernetes", 11 | classifiers = [ 12 | 'Programming Language :: Python', 13 | 'License :: OSI Approved :: Apache Software License', 14 | 'Topic :: System', 15 | 'Topic :: System :: Clustering', 16 | 'Topic :: System :: Filesystems', 17 | ], 18 | license="Apache License v2", 19 | entry_points = { 20 | 'console_scripts': [ 21 | 'kubefuse = kubefuse.kubefuse:main' 22 | ] 23 | }, 24 | install_requires = [ 25 | 'fusepy>=2.0.4', 26 | 'python-myna==1.2.0', 27 | 'PyYAML>=3', 28 | 'six>=1.10.0' 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from myna import * 2 | -------------------------------------------------------------------------------- /tests/kubectl.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Command": [ 4 | "kubectl", 5 | "describe", 6 | "pod", 7 | "graphite-dzp4q", 8 | "--namespace", 9 | "default" 10 | ], 11 | "ReturnCode": 0, 12 | "Stderr": "", 13 | "Stdout": "TmFtZToJCQkJZ3JhcGhpdGUtZHpwNHEKTmFtZXNwYWNlOgkJCWRlZmF1bHQKSW1hZ2Uocyk6CQkJaG9wc29mdC9ncmFwaGl0ZS1zdGF0c2QKTm9kZToJCQkJa3ViZXJuZXRlcy1ub2RlLTIvMTAuMjQ1LjEuNApTdGFydCBUaW1lOgkJCVR1ZSwgMTcgTWF5IDIwMTYgMTU6NDU6MjQgKzAxMDAKTGFiZWxzOgkJCQlhcHA9Z3JhcGhpdGUKU3RhdHVzOgkJCQlSdW5uaW5nClJlYXNvbjoJCQkJCk1lc3NhZ2U6CQkJCklQOgkJCQkxMC4yNDYuNjEuMwpSZXBsaWNhdGlvbiBDb250cm9sbGVyczoJZ3JhcGhpdGUgKDEvMSByZXBsaWNhcyBjcmVhdGVkKQpDb250YWluZXJzOgogIGdyYXBoaXRlOgogICAgQ29udGFpbmVyIElEOglkb2NrZXI6Ly9kM2I2MDQ3NjE1ZWEyODE1OGJjMTgwNGMyYWY4YzU3NmUwZDYwMDQyZmMxNTAwMDgzMGRkODU0ZDg0Njk2YzIzCiAgICBJbWFnZToJCWhvcHNvZnQvZ3JhcGhpdGUtc3RhdHNkCiAgICBJbWFnZSBJRDoJCWRvY2tlcjovL2Y0YTMyMzAyMGNiOGNkYzY2ODFmMWQxN2U2YzY2MGE4ODI1MmU3MTgyMjA1ZmUxNTA4ZDM0NzM5MmQ0YjJjZTUKICAgIFFvUyBUaWVyOgogICAgICBtZW1vcnk6CUd1YXJhbnRlZWQKICAgICAgY3B1OglHdWFyYW50ZWVkCiAgICBMaW1pdHM6CiAgICAgIGNwdToJMTAwbQogICAgICBtZW1vcnk6CTEwME1pCiAgICBSZXF1ZXN0czoKICAgICAgY3B1OgkJMTAwbQogICAgICBtZW1vcnk6CQkxMDBNaQogICAgU3RhdGU6CQlSdW5uaW5nCiAgICAgIFN0YXJ0ZWQ6CQlUdWUsIDE3IE1heSAyMDE2IDE1OjQ1OjI2ICswMTAwCiAgICBSZWFkeToJCVRydWUKICAgIFJlc3RhcnQgQ291bnQ6CTAKICAgIEVudmlyb25tZW50IFZhcmlhYmxlczoKQ29uZGl0aW9uczoKICBUeXBlCQlTdGF0dXMKICBSZWFkeSAJVHJ1ZSAKVm9sdW1lczoKICBkZWZhdWx0LXRva2VuLXp6MzQ5OgogICAgVHlwZToJU2VjcmV0IChhIHNlY3JldCB0aGF0IHNob3VsZCBwb3B1bGF0ZSB0aGlzIHZvbHVtZSkKICAgIFNlY3JldE5hbWU6CWRlZmF1bHQtdG9rZW4tenozNDkKTm8gZXZlbnRzLgoK" 14 | }, 15 | { 16 | "Command": [ 17 | "kubectl", 18 | "get", 19 | "namespaces" 20 | ], 21 | "ReturnCode": 0, 22 | "Stderr": "", 23 | "Stdout": "TkFNRSAgICAgICAgICBMQUJFTFMgICAgU1RBVFVTICAgIEFHRQpkZWZhdWx0ICAgICAgIDxub25lPiAgICBBY3RpdmUgICAgMmQKa3ViZS1zeXN0ZW0gICA8bm9uZT4gICAgQWN0aXZlICAgIDJkCg==" 24 | }, 25 | { 26 | "Command": [ 27 | "kubectl", 28 | "get", 29 | "ns", 30 | "-o", 31 | "yaml" 32 | ], 33 | "ReturnCode": 0, 34 | "Stderr": "", 35 | "Stdout": "YXBpVmVyc2lvbjogdjEKaXRlbXM6Ci0gYXBpVmVyc2lvbjogdjEKICBraW5kOiBOYW1lc3BhY2UKICBtZXRhZGF0YToKICAgIGNyZWF0aW9uVGltZXN0YW1wOiAyMDE2LTA1LTE3VDEyOjA2OjI2WgogICAgbmFtZTogZGVmYXVsdAogICAgcmVzb3VyY2VWZXJzaW9uOiAiNiIKICAgIHNlbGZMaW5rOiAvYXBpL3YxL25hbWVzcGFjZXMvZGVmYXVsdAogICAgdWlkOiBjODIwYjE5Ni0xYzI3LTExZTYtYWI5OS0wODAwMjc2OTk1NGEKICBzcGVjOgogICAgZmluYWxpemVyczoKICAgIC0ga3ViZXJuZXRlcwogIHN0YXR1czoKICAgIHBoYXNlOiBBY3RpdmUKLSBhcGlWZXJzaW9uOiB2MQogIGtpbmQ6IE5hbWVzcGFjZQogIG1ldGFkYXRhOgogICAgY3JlYXRpb25UaW1lc3RhbXA6IDIwMTYtMDUtMTdUMTI6MDY6MzVaCiAgICBuYW1lOiBrdWJlLXN5c3RlbQogICAgcmVzb3VyY2VWZXJzaW9uOiAiMTMiCiAgICBzZWxmTGluazogL2FwaS92MS9uYW1lc3BhY2VzL2t1YmUtc3lzdGVtCiAgICB1aWQ6IGNkODFkYjFhLTFjMjctMTFlNi1hYjk5LTA4MDAyNzY5OTU0YQogIHNwZWM6CiAgICBmaW5hbGl6ZXJzOgogICAgLSBrdWJlcm5ldGVzCiAgc3RhdHVzOgogICAgcGhhc2U6IEFjdGl2ZQpraW5kOiBMaXN0Cm1ldGFkYXRhOiB7fQo=" 36 | }, 37 | { 38 | "Command": [ 39 | "kubectl", 40 | "get", 41 | "pod", 42 | "-o", 43 | "yaml", 44 | "--namespace", 45 | "default" 46 | ], 47 | "ReturnCode": 0, 48 | "Stderr": "", 49 | "Stdout": "YXBpVmVyc2lvbjogdjEKaXRlbXM6Ci0gYXBpVmVyc2lvbjogdjEKICBraW5kOiBQb2QKICBtZXRhZGF0YToKICAgIGFubm90YXRpb25zOgogICAgICBrdWJlcm5ldGVzLmlvL2NyZWF0ZWQtYnk6IHwKICAgICAgICB7ImtpbmQiOiJTZXJpYWxpemVkUmVmZXJlbmNlIiwiYXBpVmVyc2lvbiI6InYxIiwicmVmZXJlbmNlIjp7ImtpbmQiOiJSZXBsaWNhdGlvbkNvbnRyb2xsZXIiLCJuYW1lc3BhY2UiOiJkZWZhdWx0IiwibmFtZSI6ImdyYXBoaXRlIiwidWlkIjoiZmNjNmRiZjItMWMzZC0xMWU2LWFiOTktMDgwMDI3Njk5NTRhIiwiYXBpVmVyc2lvbiI6InYxIiwicmVzb3VyY2VWZXJzaW9uIjoiMjMyNyJ9fQogICAgY3JlYXRpb25UaW1lc3RhbXA6IDIwMTYtMDUtMTdUMTQ6NDU6MjRaCiAgICBnZW5lcmF0ZU5hbWU6IGdyYXBoaXRlLQogICAgbGFiZWxzOgogICAgICBhcHA6IGdyYXBoaXRlCiAgICBuYW1lOiBncmFwaGl0ZS1kenA0cQogICAgbmFtZXNwYWNlOiBkZWZhdWx0CiAgICByZXNvdXJjZVZlcnNpb246ICI2MTIwIgogICAgc2VsZkxpbms6IC9hcGkvdjEvbmFtZXNwYWNlcy9kZWZhdWx0L3BvZHMvZ3JhcGhpdGUtZHpwNHEKICAgIHVpZDogZmNjNzZmN2MtMWMzZC0xMWU2LWFiOTktMDgwMDI3Njk5NTRhCiAgc3BlYzoKICAgIGNvbnRhaW5lcnM6CiAgICAtIGltYWdlOiBob3Bzb2Z0L2dyYXBoaXRlLXN0YXRzZAogICAgICBpbWFnZVB1bGxQb2xpY3k6IElmTm90UHJlc2VudAogICAgICBuYW1lOiBncmFwaGl0ZQogICAgICBwb3J0czoKICAgICAgLSBjb250YWluZXJQb3J0OiA4MAogICAgICAgIGhvc3RQb3J0OiA0MDAxCiAgICAgICAgcHJvdG9jb2w6IFRDUAogICAgICAtIGNvbnRhaW5lclBvcnQ6IDgxMjUKICAgICAgICBob3N0UG9ydDogODEyNQogICAgICAgIHByb3RvY29sOiBVRFAKICAgICAgcmVzb3VyY2VzOgogICAgICAgIGxpbWl0czoKICAgICAgICAgIGNwdTogMTAwbQogICAgICAgICAgbWVtb3J5OiAxMDBNaQogICAgICAgIHJlcXVlc3RzOgogICAgICAgICAgY3B1OiAxMDBtCiAgICAgICAgICBtZW1vcnk6IDEwME1pCiAgICAgIHRlcm1pbmF0aW9uTWVzc2FnZVBhdGg6IC9kZXYvdGVybWluYXRpb24tbG9nCiAgICAgIHZvbHVtZU1vdW50czoKICAgICAgLSBtb3VudFBhdGg6IC92YXIvcnVuL3NlY3JldHMva3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudAogICAgICAgIG5hbWU6IGRlZmF1bHQtdG9rZW4tenozNDkKICAgICAgICByZWFkT25seTogdHJ1ZQogICAgZG5zUG9saWN5OiBDbHVzdGVyRmlyc3QKICAgIG5vZGVOYW1lOiBrdWJlcm5ldGVzLW5vZGUtMgogICAgcmVzdGFydFBvbGljeTogQWx3YXlzCiAgICBzZXJ2aWNlQWNjb3VudDogZGVmYXVsdAogICAgc2VydmljZUFjY291bnROYW1lOiBkZWZhdWx0CiAgICB0ZXJtaW5hdGlvbkdyYWNlUGVyaW9kU2Vjb25kczogMzAKICAgIHZvbHVtZXM6CiAgICAtIG5hbWU6IGRlZmF1bHQtdG9rZW4tenozNDkKICAgICAgc2VjcmV0OgogICAgICAgIHNlY3JldE5hbWU6IGRlZmF1bHQtdG9rZW4tenozNDkKICBzdGF0dXM6CiAgICBjb25kaXRpb25zOgogICAgLSBsYXN0UHJvYmVUaW1lOiBudWxsCiAgICAgIGxhc3RUcmFuc2l0aW9uVGltZTogMjAxNi0wNS0xN1QxNDo0NToyNloKICAgICAgc3RhdHVzOiAiVHJ1ZSIKICAgICAgdHlwZTogUmVhZHkKICAgIGNvbnRhaW5lclN0YXR1c2VzOgogICAgLSBjb250YWluZXJJRDogZG9ja2VyOi8vZDNiNjA0NzYxNWVhMjgxNThiYzE4MDRjMmFmOGM1NzZlMGQ2MDA0MmZjMTUwMDA4MzBkZDg1NGQ4NDY5NmMyMwogICAgICBpbWFnZTogaG9wc29mdC9ncmFwaGl0ZS1zdGF0c2QKICAgICAgaW1hZ2VJRDogZG9ja2VyOi8vZjRhMzIzMDIwY2I4Y2RjNjY4MWYxZDE3ZTZjNjYwYTg4MjUyZTcxODIyMDVmZTE1MDhkMzQ3MzkyZDRiMmNlNQogICAgICBsYXN0U3RhdGU6IHt9CiAgICAgIG5hbWU6IGdyYXBoaXRlCiAgICAgIHJlYWR5OiB0cnVlCiAgICAgIHJlc3RhcnRDb3VudDogMAogICAgICBzdGF0ZToKICAgICAgICBydW5uaW5nOgogICAgICAgICAgc3RhcnRlZEF0OiAyMDE2LTA1LTE3VDE0OjQ1OjI2WgogICAgaG9zdElQOiAxMC4yNDUuMS40CiAgICBwaGFzZTogUnVubmluZwogICAgcG9kSVA6IDEwLjI0Ni42MS4zCiAgICBzdGFydFRpbWU6IDIwMTYtMDUtMTdUMTQ6NDU6MjRaCi0gYXBpVmVyc2lvbjogdjEKICBraW5kOiBQb2QKICBtZXRhZGF0YToKICAgIGFubm90YXRpb25zOgogICAgICBrdWJlcm5ldGVzLmlvL2NyZWF0ZWQtYnk6IHwKICAgICAgICB7ImtpbmQiOiJTZXJpYWxpemVkUmVmZXJlbmNlIiwiYXBpVmVyc2lvbiI6InYxIiwicmVmZXJlbmNlIjp7ImtpbmQiOiJSZXBsaWNhdGlvbkNvbnRyb2xsZXIiLCJuYW1lc3BhY2UiOiJkZWZhdWx0IiwibmFtZSI6InBvc3RncmVzIiwidWlkIjoiZjg4NmFmYWUtMWMzZC0xMWU2LWFiOTktMDgwMDI3Njk5NTRhIiwiYXBpVmVyc2lvbiI6InYxIiwicmVzb3VyY2VWZXJzaW9uIjoiMjMxMyJ9fQogICAgY3JlYXRpb25UaW1lc3RhbXA6IDIwMTYtMDUtMTdUMTQ6NDU6MTZaCiAgICBnZW5lcmF0ZU5hbWU6IHBvc3RncmVzLQogICAgbGFiZWxzOgogICAgICBhcHA6IHBvc3RncmVzCiAgICBuYW1lOiBwb3N0Z3Jlcy01ajNtdQogICAgbmFtZXNwYWNlOiBkZWZhdWx0CiAgICByZXNvdXJjZVZlcnNpb246ICI2MTI1IgogICAgc2VsZkxpbms6IC9hcGkvdjEvbmFtZXNwYWNlcy9kZWZhdWx0L3BvZHMvcG9zdGdyZXMtNWozbXUKICAgIHVpZDogZjg4NzRkOWMtMWMzZC0xMWU2LWFiOTktMDgwMDI3Njk5NTRhCiAgc3BlYzoKICAgIGNvbnRhaW5lcnM6CiAgICAtIGltYWdlOiBwb3N0Z3JlcwogICAgICBpbWFnZVB1bGxQb2xpY3k6IElmTm90UHJlc2VudAogICAgICBuYW1lOiBwb3N0Z3JlcwogICAgICBwb3J0czoKICAgICAgLSBjb250YWluZXJQb3J0OiA1NDMyCiAgICAgICAgaG9zdFBvcnQ6IDU0MzIKICAgICAgICBwcm90b2NvbDogVENQCiAgICAgIHJlc291cmNlczoKICAgICAgICBsaW1pdHM6CiAgICAgICAgICBjcHU6IDEwMG0KICAgICAgICAgIG1lbW9yeTogNTAwTWkKICAgICAgICByZXF1ZXN0czoKICAgICAgICAgIGNwdTogMTAwbQogICAgICAgICAgbWVtb3J5OiA1MDBNaQogICAgICB0ZXJtaW5hdGlvbk1lc3NhZ2VQYXRoOiAvZGV2L3Rlcm1pbmF0aW9uLWxvZwogICAgICB2b2x1bWVNb3VudHM6CiAgICAgIC0gbW91bnRQYXRoOiAvdmFyL3J1bi9zZWNyZXRzL2t1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQKICAgICAgICBuYW1lOiBkZWZhdWx0LXRva2VuLXp6MzQ5CiAgICAgICAgcmVhZE9ubHk6IHRydWUKICAgIGRuc1BvbGljeTogQ2x1c3RlckZpcnN0CiAgICBub2RlTmFtZToga3ViZXJuZXRlcy1ub2RlLTIKICAgIHJlc3RhcnRQb2xpY3k6IEFsd2F5cwogICAgc2VydmljZUFjY291bnQ6IGRlZmF1bHQKICAgIHNlcnZpY2VBY2NvdW50TmFtZTogZGVmYXVsdAogICAgdGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHM6IDMwCiAgICB2b2x1bWVzOgogICAgLSBuYW1lOiBkZWZhdWx0LXRva2VuLXp6MzQ5CiAgICAgIHNlY3JldDoKICAgICAgICBzZWNyZXROYW1lOiBkZWZhdWx0LXRva2VuLXp6MzQ5CiAgc3RhdHVzOgogICAgY29uZGl0aW9uczoKICAgIC0gbGFzdFByb2JlVGltZTogbnVsbAogICAgICBsYXN0VHJhbnNpdGlvblRpbWU6IDIwMTYtMDUtMTdUMTQ6NDU6MTlaCiAgICAgIHN0YXR1czogIlRydWUiCiAgICAgIHR5cGU6IFJlYWR5CiAgICBjb250YWluZXJTdGF0dXNlczoKICAgIC0gY29udGFpbmVySUQ6IGRvY2tlcjovLzUyNTkyNjBlZjA2MTcxNDEyMTBhNDQ3MzUwM2I4MDE2NWUxMzEyNmI3YmNlOTI0ZTRiOTQxNTMzN2U4NDBmMmUKICAgICAgaW1hZ2U6IHBvc3RncmVzCiAgICAgIGltYWdlSUQ6IGRvY2tlcjovL2I1N2EzMmUxNWRlY2EzZTg2MGFlYTgxNzk4MzIzZDE3ZWU3NDY5YzBhZjdlN2ZkMDIzZjIyNzk1NDY1M2RlZmMKICAgICAgbGFzdFN0YXRlOiB7fQogICAgICBuYW1lOiBwb3N0Z3JlcwogICAgICByZWFkeTogdHJ1ZQogICAgICByZXN0YXJ0Q291bnQ6IDAKICAgICAgc3RhdGU6CiAgICAgICAgcnVubmluZzoKICAgICAgICAgIHN0YXJ0ZWRBdDogMjAxNi0wNS0xN1QxNDo0NToxOFoKICAgIGhvc3RJUDogMTAuMjQ1LjEuNAogICAgcGhhc2U6IFJ1bm5pbmcKICAgIHBvZElQOiAxMC4yNDYuNjEuMgogICAgc3RhcnRUaW1lOiAyMDE2LTA1LTE3VDE0OjQ1OjE2WgotIGFwaVZlcnNpb246IHYxCiAga2luZDogUG9kCiAgbWV0YWRhdGE6CiAgICBhbm5vdGF0aW9uczoKICAgICAga3ViZXJuZXRlcy5pby9jcmVhdGVkLWJ5OiB8CiAgICAgICAgeyJraW5kIjoiU2VyaWFsaXplZFJlZmVyZW5jZSIsImFwaVZlcnNpb24iOiJ2MSIsInJlZmVyZW5jZSI6eyJraW5kIjoiUmVwbGljYXRpb25Db250cm9sbGVyIiwibmFtZXNwYWNlIjoiZGVmYXVsdCIsIm5hbWUiOiJyYWJiaXRtcSIsInVpZCI6IjAzYmM2MDIyLTFjM2UtMTFlNi1hYjk5LTA4MDAyNzY5OTU0YSIsImFwaVZlcnNpb24iOiJ2MSIsInJlc291cmNlVmVyc2lvbiI6IjIzNDEifX0KICAgIGNyZWF0aW9uVGltZXN0YW1wOiAyMDE2LTA1LTE3VDE0OjQ1OjM1WgogICAgZ2VuZXJhdGVOYW1lOiByYWJiaXRtcS0KICAgIGxhYmVsczoKICAgICAgYXBwOiByYWJiaXRtcQogICAgbmFtZTogcmFiYml0bXEtMXZlankKICAgIG5hbWVzcGFjZTogZGVmYXVsdAogICAgcmVzb3VyY2VWZXJzaW9uOiAiNjEyMiIKICAgIHNlbGZMaW5rOiAvYXBpL3YxL25hbWVzcGFjZXMvZGVmYXVsdC9wb2RzL3JhYmJpdG1xLTF2ZWp5CiAgICB1aWQ6IDAzYmQwM2ZhLTFjM2UtMTFlNi1hYjk5LTA4MDAyNzY5OTU0YQogIHNwZWM6CiAgICBjb250YWluZXJzOgogICAgLSBpbWFnZTogcmFiYml0bXEKICAgICAgaW1hZ2VQdWxsUG9saWN5OiBJZk5vdFByZXNlbnQKICAgICAgbmFtZTogcmFiYml0bXEKICAgICAgcG9ydHM6CiAgICAgIC0gY29udGFpbmVyUG9ydDogNTY3MgogICAgICAgIGhvc3RQb3J0OiA1NjcyCiAgICAgICAgcHJvdG9jb2w6IFRDUAogICAgICByZXNvdXJjZXM6CiAgICAgICAgbGltaXRzOgogICAgICAgICAgY3B1OiAxMDBtCiAgICAgICAgICBtZW1vcnk6IDEwME1pCiAgICAgICAgcmVxdWVzdHM6CiAgICAgICAgICBjcHU6IDEwMG0KICAgICAgICAgIG1lbW9yeTogMTAwTWkKICAgICAgdGVybWluYXRpb25NZXNzYWdlUGF0aDogL2Rldi90ZXJtaW5hdGlvbi1sb2cKICAgICAgdm9sdW1lTW91bnRzOgogICAgICAtIG1vdW50UGF0aDogL3Zhci9ydW4vc2VjcmV0cy9rdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50CiAgICAgICAgbmFtZTogZGVmYXVsdC10b2tlbi16ejM0OQogICAgICAgIHJlYWRPbmx5OiB0cnVlCiAgICBkbnNQb2xpY3k6IENsdXN0ZXJGaXJzdAogICAgbm9kZU5hbWU6IGt1YmVybmV0ZXMtbm9kZS0yCiAgICByZXN0YXJ0UG9saWN5OiBBbHdheXMKICAgIHNlcnZpY2VBY2NvdW50OiBkZWZhdWx0CiAgICBzZXJ2aWNlQWNjb3VudE5hbWU6IGRlZmF1bHQKICAgIHRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzOiAzMAogICAgdm9sdW1lczoKICAgIC0gbmFtZTogZGVmYXVsdC10b2tlbi16ejM0OQogICAgICBzZWNyZXQ6CiAgICAgICAgc2VjcmV0TmFtZTogZGVmYXVsdC10b2tlbi16ejM0OQogIHN0YXR1czoKICAgIGNvbmRpdGlvbnM6CiAgICAtIGxhc3RQcm9iZVRpbWU6IG51bGwKICAgICAgbGFzdFRyYW5zaXRpb25UaW1lOiAyMDE2LTA1LTE3VDE0OjQ1OjUxWgogICAgICBzdGF0dXM6ICJUcnVlIgogICAgICB0eXBlOiBSZWFkeQogICAgY29udGFpbmVyU3RhdHVzZXM6CiAgICAtIGNvbnRhaW5lcklEOiBkb2NrZXI6Ly84ODc2ZGY0NzI5ZDhiZTY3ZmZhMjk2ZTcxMTZjZjAyYWNhNDNlNTBkYzQ5MjAwNTIyMmY1N2E3ZmZkN2Q5NzcwCiAgICAgIGltYWdlOiByYWJiaXRtcQogICAgICBpbWFnZUlEOiBkb2NrZXI6Ly9lZDZhYjIzZmVlZDEzZTk3YTY4MGM2N2JkMTRhMWE4MmMzYjQ2ODc4Y2FlNTc5YzdhYWE1MzBkMWUzNjI3OGZiCiAgICAgIGxhc3RTdGF0ZToge30KICAgICAgbmFtZTogcmFiYml0bXEKICAgICAgcmVhZHk6IHRydWUKICAgICAgcmVzdGFydENvdW50OiAwCiAgICAgIHN0YXRlOgogICAgICAgIHJ1bm5pbmc6CiAgICAgICAgICBzdGFydGVkQXQ6IDIwMTYtMDUtMTdUMTQ6NDU6NTFaCiAgICBob3N0SVA6IDEwLjI0NS4xLjQKICAgIHBoYXNlOiBSdW5uaW5nCiAgICBwb2RJUDogMTAuMjQ2LjYxLjQKICAgIHN0YXJ0VGltZTogMjAxNi0wNS0xN1QxNDo0NTozNVoKa2luZDogTGlzdAptZXRhZGF0YToge30K" 50 | }, 51 | { 52 | "Command": [ 53 | "kubectl", 54 | "get", 55 | "pod", 56 | "graphite-dzp4q", 57 | "-o", 58 | "json", 59 | "--namespace", 60 | "default" 61 | ], 62 | "ReturnCode": 0, 63 | "Stderr": "", 64 | "Stdout": "ewogICAgImtpbmQiOiAiUG9kIiwKICAgICJhcGlWZXJzaW9uIjogInYxIiwKICAgICJtZXRhZGF0YSI6IHsKICAgICAgICAibmFtZSI6ICJncmFwaGl0ZS1kenA0cSIsCiAgICAgICAgImdlbmVyYXRlTmFtZSI6ICJncmFwaGl0ZS0iLAogICAgICAgICJuYW1lc3BhY2UiOiAiZGVmYXVsdCIsCiAgICAgICAgInNlbGZMaW5rIjogIi9hcGkvdjEvbmFtZXNwYWNlcy9kZWZhdWx0L3BvZHMvZ3JhcGhpdGUtZHpwNHEiLAogICAgICAgICJ1aWQiOiAiZmNjNzZmN2MtMWMzZC0xMWU2LWFiOTktMDgwMDI3Njk5NTRhIiwKICAgICAgICAicmVzb3VyY2VWZXJzaW9uIjogIjYxMjAiLAogICAgICAgICJjcmVhdGlvblRpbWVzdGFtcCI6ICIyMDE2LTA1LTE3VDE0OjQ1OjI0WiIsCiAgICAgICAgImxhYmVscyI6IHsKICAgICAgICAgICAgImFwcCI6ICJncmFwaGl0ZSIKICAgICAgICB9LAogICAgICAgICJhbm5vdGF0aW9ucyI6IHsKICAgICAgICAgICAgImt1YmVybmV0ZXMuaW8vY3JlYXRlZC1ieSI6ICJ7XCJraW5kXCI6XCJTZXJpYWxpemVkUmVmZXJlbmNlXCIsXCJhcGlWZXJzaW9uXCI6XCJ2MVwiLFwicmVmZXJlbmNlXCI6e1wia2luZFwiOlwiUmVwbGljYXRpb25Db250cm9sbGVyXCIsXCJuYW1lc3BhY2VcIjpcImRlZmF1bHRcIixcIm5hbWVcIjpcImdyYXBoaXRlXCIsXCJ1aWRcIjpcImZjYzZkYmYyLTFjM2QtMTFlNi1hYjk5LTA4MDAyNzY5OTU0YVwiLFwiYXBpVmVyc2lvblwiOlwidjFcIixcInJlc291cmNlVmVyc2lvblwiOlwiMjMyN1wifX1cbiIKICAgICAgICB9CiAgICB9LAogICAgInNwZWMiOiB7CiAgICAgICAgInZvbHVtZXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJuYW1lIjogImRlZmF1bHQtdG9rZW4tenozNDkiLAogICAgICAgICAgICAgICAgInNlY3JldCI6IHsKICAgICAgICAgICAgICAgICAgICAic2VjcmV0TmFtZSI6ICJkZWZhdWx0LXRva2VuLXp6MzQ5IgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgXSwKICAgICAgICAiY29udGFpbmVycyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm5hbWUiOiAiZ3JhcGhpdGUiLAogICAgICAgICAgICAgICAgImltYWdlIjogImhvcHNvZnQvZ3JhcGhpdGUtc3RhdHNkIiwKICAgICAgICAgICAgICAgICJwb3J0cyI6IFsKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICJob3N0UG9ydCI6IDQwMDEsCiAgICAgICAgICAgICAgICAgICAgICAgICJjb250YWluZXJQb3J0IjogODAsCiAgICAgICAgICAgICAgICAgICAgICAgICJwcm90b2NvbCI6ICJUQ1AiCiAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICJob3N0UG9ydCI6IDgxMjUsCiAgICAgICAgICAgICAgICAgICAgICAgICJjb250YWluZXJQb3J0IjogODEyNSwKICAgICAgICAgICAgICAgICAgICAgICAgInByb3RvY29sIjogIlVEUCIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgInJlc291cmNlcyI6IHsKICAgICAgICAgICAgICAgICAgICAibGltaXRzIjogewogICAgICAgICAgICAgICAgICAgICAgICAiY3B1IjogIjEwMG0iLAogICAgICAgICAgICAgICAgICAgICAgICAibWVtb3J5IjogIjEwME1pIgogICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgInJlcXVlc3RzIjogewogICAgICAgICAgICAgICAgICAgICAgICAiY3B1IjogIjEwMG0iLAogICAgICAgICAgICAgICAgICAgICAgICAibWVtb3J5IjogIjEwME1pIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAidm9sdW1lTW91bnRzIjogWwogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiZGVmYXVsdC10b2tlbi16ejM0OSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUsCiAgICAgICAgICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9rdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50IgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAidGVybWluYXRpb25NZXNzYWdlUGF0aCI6ICIvZGV2L3Rlcm1pbmF0aW9uLWxvZyIsCiAgICAgICAgICAgICAgICAiaW1hZ2VQdWxsUG9saWN5IjogIklmTm90UHJlc2VudCIKICAgICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgInJlc3RhcnRQb2xpY3kiOiAiQWx3YXlzIiwKICAgICAgICAidGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHMiOiAzMCwKICAgICAgICAiZG5zUG9saWN5IjogIkNsdXN0ZXJGaXJzdCIsCiAgICAgICAgInNlcnZpY2VBY2NvdW50TmFtZSI6ICJkZWZhdWx0IiwKICAgICAgICAic2VydmljZUFjY291bnQiOiAiZGVmYXVsdCIsCiAgICAgICAgIm5vZGVOYW1lIjogImt1YmVybmV0ZXMtbm9kZS0yIgogICAgfSwKICAgICJzdGF0dXMiOiB7CiAgICAgICAgInBoYXNlIjogIlJ1bm5pbmciLAogICAgICAgICJjb25kaXRpb25zIjogWwogICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAidHlwZSI6ICJSZWFkeSIsCiAgICAgICAgICAgICAgICAic3RhdHVzIjogIlRydWUiLAogICAgICAgICAgICAgICAgImxhc3RQcm9iZVRpbWUiOiBudWxsLAogICAgICAgICAgICAgICAgImxhc3RUcmFuc2l0aW9uVGltZSI6ICIyMDE2LTA1LTE3VDE0OjQ1OjI2WiIKICAgICAgICAgICAgfQogICAgICAgIF0sCiAgICAgICAgImhvc3RJUCI6ICIxMC4yNDUuMS40IiwKICAgICAgICAicG9kSVAiOiAiMTAuMjQ2LjYxLjMiLAogICAgICAgICJzdGFydFRpbWUiOiAiMjAxNi0wNS0xN1QxNDo0NToyNFoiLAogICAgICAgICJjb250YWluZXJTdGF0dXNlcyI6IFsKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm5hbWUiOiAiZ3JhcGhpdGUiLAogICAgICAgICAgICAgICAgInN0YXRlIjogewogICAgICAgICAgICAgICAgICAgICJydW5uaW5nIjogewogICAgICAgICAgICAgICAgICAgICAgICAic3RhcnRlZEF0IjogIjIwMTYtMDUtMTdUMTQ6NDU6MjZaIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAibGFzdFN0YXRlIjoge30sCiAgICAgICAgICAgICAgICAicmVhZHkiOiB0cnVlLAogICAgICAgICAgICAgICAgInJlc3RhcnRDb3VudCI6IDAsCiAgICAgICAgICAgICAgICAiaW1hZ2UiOiAiaG9wc29mdC9ncmFwaGl0ZS1zdGF0c2QiLAogICAgICAgICAgICAgICAgImltYWdlSUQiOiAiZG9ja2VyOi8vZjRhMzIzMDIwY2I4Y2RjNjY4MWYxZDE3ZTZjNjYwYTg4MjUyZTcxODIyMDVmZTE1MDhkMzQ3MzkyZDRiMmNlNSIsCiAgICAgICAgICAgICAgICAiY29udGFpbmVySUQiOiAiZG9ja2VyOi8vZDNiNjA0NzYxNWVhMjgxNThiYzE4MDRjMmFmOGM1NzZlMGQ2MDA0MmZjMTUwMDA4MzBkZDg1NGQ4NDY5NmMyMyIKICAgICAgICAgICAgfQogICAgICAgIF0KICAgIH0KfQo=" 65 | }, 66 | { 67 | "Command": [ 68 | "kubectl", 69 | "get", 70 | "pod", 71 | "graphite-dzp4q", 72 | "-o", 73 | "yaml", 74 | "--namespace", 75 | "default" 76 | ], 77 | "ReturnCode": 0, 78 | "Stderr": "", 79 | "Stdout": "YXBpVmVyc2lvbjogdjEKa2luZDogUG9kCm1ldGFkYXRhOgogIGFubm90YXRpb25zOgogICAga3ViZXJuZXRlcy5pby9jcmVhdGVkLWJ5OiB8CiAgICAgIHsia2luZCI6IlNlcmlhbGl6ZWRSZWZlcmVuY2UiLCJhcGlWZXJzaW9uIjoidjEiLCJyZWZlcmVuY2UiOnsia2luZCI6IlJlcGxpY2F0aW9uQ29udHJvbGxlciIsIm5hbWVzcGFjZSI6ImRlZmF1bHQiLCJuYW1lIjoiZ3JhcGhpdGUiLCJ1aWQiOiJmY2M2ZGJmMi0xYzNkLTExZTYtYWI5OS0wODAwMjc2OTk1NGEiLCJhcGlWZXJzaW9uIjoidjEiLCJyZXNvdXJjZVZlcnNpb24iOiIyMzI3In19CiAgY3JlYXRpb25UaW1lc3RhbXA6IDIwMTYtMDUtMTdUMTQ6NDU6MjRaCiAgZ2VuZXJhdGVOYW1lOiBncmFwaGl0ZS0KICBsYWJlbHM6CiAgICBhcHA6IGdyYXBoaXRlCiAgbmFtZTogZ3JhcGhpdGUtZHpwNHEKICBuYW1lc3BhY2U6IGRlZmF1bHQKICByZXNvdXJjZVZlcnNpb246ICI2MTIwIgogIHNlbGZMaW5rOiAvYXBpL3YxL25hbWVzcGFjZXMvZGVmYXVsdC9wb2RzL2dyYXBoaXRlLWR6cDRxCiAgdWlkOiBmY2M3NmY3Yy0xYzNkLTExZTYtYWI5OS0wODAwMjc2OTk1NGEKc3BlYzoKICBjb250YWluZXJzOgogIC0gaW1hZ2U6IGhvcHNvZnQvZ3JhcGhpdGUtc3RhdHNkCiAgICBpbWFnZVB1bGxQb2xpY3k6IElmTm90UHJlc2VudAogICAgbmFtZTogZ3JhcGhpdGUKICAgIHBvcnRzOgogICAgLSBjb250YWluZXJQb3J0OiA4MAogICAgICBob3N0UG9ydDogNDAwMQogICAgICBwcm90b2NvbDogVENQCiAgICAtIGNvbnRhaW5lclBvcnQ6IDgxMjUKICAgICAgaG9zdFBvcnQ6IDgxMjUKICAgICAgcHJvdG9jb2w6IFVEUAogICAgcmVzb3VyY2VzOgogICAgICBsaW1pdHM6CiAgICAgICAgY3B1OiAxMDBtCiAgICAgICAgbWVtb3J5OiAxMDBNaQogICAgICByZXF1ZXN0czoKICAgICAgICBjcHU6IDEwMG0KICAgICAgICBtZW1vcnk6IDEwME1pCiAgICB0ZXJtaW5hdGlvbk1lc3NhZ2VQYXRoOiAvZGV2L3Rlcm1pbmF0aW9uLWxvZwogICAgdm9sdW1lTW91bnRzOgogICAgLSBtb3VudFBhdGg6IC92YXIvcnVuL3NlY3JldHMva3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudAogICAgICBuYW1lOiBkZWZhdWx0LXRva2VuLXp6MzQ5CiAgICAgIHJlYWRPbmx5OiB0cnVlCiAgZG5zUG9saWN5OiBDbHVzdGVyRmlyc3QKICBub2RlTmFtZToga3ViZXJuZXRlcy1ub2RlLTIKICByZXN0YXJ0UG9saWN5OiBBbHdheXMKICBzZXJ2aWNlQWNjb3VudDogZGVmYXVsdAogIHNlcnZpY2VBY2NvdW50TmFtZTogZGVmYXVsdAogIHRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzOiAzMAogIHZvbHVtZXM6CiAgLSBuYW1lOiBkZWZhdWx0LXRva2VuLXp6MzQ5CiAgICBzZWNyZXQ6CiAgICAgIHNlY3JldE5hbWU6IGRlZmF1bHQtdG9rZW4tenozNDkKc3RhdHVzOgogIGNvbmRpdGlvbnM6CiAgLSBsYXN0UHJvYmVUaW1lOiBudWxsCiAgICBsYXN0VHJhbnNpdGlvblRpbWU6IDIwMTYtMDUtMTdUMTQ6NDU6MjZaCiAgICBzdGF0dXM6ICJUcnVlIgogICAgdHlwZTogUmVhZHkKICBjb250YWluZXJTdGF0dXNlczoKICAtIGNvbnRhaW5lcklEOiBkb2NrZXI6Ly9kM2I2MDQ3NjE1ZWEyODE1OGJjMTgwNGMyYWY4YzU3NmUwZDYwMDQyZmMxNTAwMDgzMGRkODU0ZDg0Njk2YzIzCiAgICBpbWFnZTogaG9wc29mdC9ncmFwaGl0ZS1zdGF0c2QKICAgIGltYWdlSUQ6IGRvY2tlcjovL2Y0YTMyMzAyMGNiOGNkYzY2ODFmMWQxN2U2YzY2MGE4ODI1MmU3MTgyMjA1ZmUxNTA4ZDM0NzM5MmQ0YjJjZTUKICAgIGxhc3RTdGF0ZToge30KICAgIG5hbWU6IGdyYXBoaXRlCiAgICByZWFkeTogdHJ1ZQogICAgcmVzdGFydENvdW50OiAwCiAgICBzdGF0ZToKICAgICAgcnVubmluZzoKICAgICAgICBzdGFydGVkQXQ6IDIwMTYtMDUtMTdUMTQ6NDU6MjZaCiAgaG9zdElQOiAxMC4yNDUuMS40CiAgcGhhc2U6IFJ1bm5pbmcKICBwb2RJUDogMTAuMjQ2LjYxLjMKICBzdGFydFRpbWU6IDIwMTYtMDUtMTdUMTQ6NDU6MjRaCg==" 80 | }, 81 | { 82 | "Command": [ 83 | "kubectl", 84 | "get", 85 | "pods", 86 | "--all-namespaces" 87 | ], 88 | "ReturnCode": 0, 89 | "Stderr": "", 90 | "Stdout": "TkFNRVNQQUNFICAgICBOQU1FICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSRUFEWSAgICAgU1RBVFVTICAgIFJFU1RBUlRTICAgQUdFCmRlZmF1bHQgICAgICAgZ3JhcGhpdGUtZHpwNHEgICAgICAgICAgICAgICAgICAgICAgICAgMS8xICAgICAgIFJ1bm5pbmcgICAwICAgICAgICAgIDJkCmRlZmF1bHQgICAgICAgcG9zdGdyZXMtNWozbXUgICAgICAgICAgICAgICAgICAgICAgICAgMS8xICAgICAgIFJ1bm5pbmcgICAwICAgICAgICAgIDJkCmRlZmF1bHQgICAgICAgcmFiYml0bXEtMXZlankgICAgICAgICAgICAgICAgICAgICAgICAgMS8xICAgICAgIFJ1bm5pbmcgICAwICAgICAgICAgIDJkCmt1YmUtc3lzdGVtICAgaGVhcHN0ZXItdjEuMC4yLTE4MjAyMTc0ODctYWt6NDggICAgICAgNC80ICAgICAgIFJ1bm5pbmcgICAwICAgICAgICAgIDJkCmt1YmUtc3lzdGVtICAga3ViZS1kbnMtdjExLXIzdzk2ICAgICAgICAgICAgICAgICAgICAgNC80ICAgICAgIFJ1bm5pbmcgICAwICAgICAgICAgIDJkCmt1YmUtc3lzdGVtICAga3ViZS1wcm94eS1rdWJlcm5ldGVzLW5vZGUtMSAgICAgICAgICAgMS8xICAgICAgIFJ1bm5pbmcgICAwICAgICAgICAgIDJkCmt1YmUtc3lzdGVtICAga3ViZS1wcm94eS1rdWJlcm5ldGVzLW5vZGUtMiAgICAgICAgICAgMS8xICAgICAgIFJ1bm5pbmcgICAwICAgICAgICAgIDJkCmt1YmUtc3lzdGVtICAga3ViZXJuZXRlcy1kYXNoYm9hcmQtdjEuMC4xLTFqYjlrICAgICAgMS8xICAgICAgIFJ1bm5pbmcgICAwICAgICAgICAgIDJkCmt1YmUtc3lzdGVtICAgbW9uaXRvcmluZy1pbmZsdXhkYi1ncmFmYW5hLXYzLXk5eWtwICAgMi8yICAgICAgIFJ1bm5pbmcgICAwICAgICAgICAgIDJkCg==" 91 | }, 92 | { 93 | "Command": [ 94 | "kubectl", 95 | "get", 96 | "rc", 97 | "--all-namespaces" 98 | ], 99 | "ReturnCode": 0, 100 | "Stderr": "", 101 | "Stdout": "TkFNRVNQQUNFICAgICBDT05UUk9MTEVSICAgICAgICAgICAgICAgICAgICAgICBDT05UQUlORVIoUykgICAgICAgICAgIElNQUdFKFMpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTRUxFQ1RPUiAgICAgICAgICAgICAgICAgICAgICAgICAgIFJFUExJQ0FTICAgQUdFCmRlZmF1bHQgICAgICAgZ3JhcGhpdGUgICAgICAgICAgICAgICAgICAgICAgICAgZ3JhcGhpdGUgICAgICAgICAgICAgICBob3Bzb2Z0L2dyYXBoaXRlLXN0YXRzZCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXBwPWdyYXBoaXRlICAgICAgICAgICAgICAgICAgICAgICAxICAgICAgICAgIDJkCmRlZmF1bHQgICAgICAgcG9zdGdyZXMgICAgICAgICAgICAgICAgICAgICAgICAgcG9zdGdyZXMgICAgICAgICAgICAgICBwb3N0Z3JlcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXBwPXBvc3RncmVzICAgICAgICAgICAgICAgICAgICAgICAxICAgICAgICAgIDJkCmRlZmF1bHQgICAgICAgcmFiYml0bXEgICAgICAgICAgICAgICAgICAgICAgICAgcmFiYml0bXEgICAgICAgICAgICAgICByYWJiaXRtcSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXBwPXJhYmJpdG1xICAgICAgICAgICAgICAgICAgICAgICAxICAgICAgICAgIDJkCmt1YmUtc3lzdGVtICAga3ViZS1kbnMtdjExICAgICAgICAgICAgICAgICAgICAgZXRjZCAgICAgICAgICAgICAgICAgICBnY3IuaW8vZ29vZ2xlX2NvbnRhaW5lcnMvZXRjZC1hbWQ2NDoyLjIuMSAgICAgICAgICAgICAgICAgICAgazhzLWFwcD1rdWJlLWRucyx2ZXJzaW9uPXYxMSAgICAgICAxICAgICAgICAgIDJkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga3ViZTJza3kgICAgICAgICAgICAgICBnY3IuaW8vZ29vZ2xlX2NvbnRhaW5lcnMva3ViZTJza3k6MS4xNCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBza3lkbnMgICAgICAgICAgICAgICAgIGdjci5pby9nb29nbGVfY29udGFpbmVycy9za3lkbnM6MjAxNS0xMC0xMy04YzcyZjhjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWx0aHogICAgICAgICAgICAgICAgZ2NyLmlvL2dvb2dsZV9jb250YWluZXJzL2V4ZWNoZWFsdGh6OjEuMCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCmt1YmUtc3lzdGVtICAga3ViZXJuZXRlcy1kYXNoYm9hcmQtdjEuMC4xICAgICAga3ViZXJuZXRlcy1kYXNoYm9hcmQgICBnY3IuaW8vZ29vZ2xlX2NvbnRhaW5lcnMva3ViZXJuZXRlcy1kYXNoYm9hcmQtYW1kNjQ6djEuMC4xICAgazhzLWFwcD1rdWJlcm5ldGVzLWRhc2hib2FyZCAgICAgICAxICAgICAgICAgMmQKa3ViZS1zeXN0ZW0gICBtb25pdG9yaW5nLWluZmx1eGRiLWdyYWZhbmEtdjMgICBpbmZsdXhkYiAgICAgICAgICAgICAgIGdjci5pby9nb29nbGVfY29udGFpbmVycy9oZWFwc3Rlcl9pbmZsdXhkYjp2MC41ICAgICAgICAgICAgICBrOHMtYXBwPWluZmx1eEdyYWZhbmEsdmVyc2lvbj12MyAgIDEgICAgICAgICAyZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyYWZhbmEgICAgICAgICAgICAgICAgZ2NyLmlvL2dvb2dsZV9jb250YWluZXJzL2hlYXBzdGVyX2dyYWZhbmE6djIuNi4wLTIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCg==" 102 | }, 103 | { 104 | "Command": [ 105 | "kubectl", 106 | "get", 107 | "rc", 108 | "-o", 109 | "yaml", 110 | "--namespace", 111 | "default" 112 | ], 113 | "ReturnCode": 0, 114 | "Stderr": "", 115 | "Stdout": "YXBpVmVyc2lvbjogdjEKaXRlbXM6Ci0gYXBpVmVyc2lvbjogdjEKICBraW5kOiBSZXBsaWNhdGlvbkNvbnRyb2xsZXIKICBtZXRhZGF0YToKICAgIGNyZWF0aW9uVGltZXN0YW1wOiAyMDE2LTA1LTE3VDE0OjQ1OjI0WgogICAgZ2VuZXJhdGlvbjogMQogICAgbGFiZWxzOgogICAgICBhcHA6IGdyYXBoaXRlCiAgICBuYW1lOiBncmFwaGl0ZQogICAgbmFtZXNwYWNlOiBkZWZhdWx0CiAgICByZXNvdXJjZVZlcnNpb246ICIyMzMxIgogICAgc2VsZkxpbms6IC9hcGkvdjEvbmFtZXNwYWNlcy9kZWZhdWx0L3JlcGxpY2F0aW9uY29udHJvbGxlcnMvZ3JhcGhpdGUKICAgIHVpZDogZmNjNmRiZjItMWMzZC0xMWU2LWFiOTktMDgwMDI3Njk5NTRhCiAgc3BlYzoKICAgIHJlcGxpY2FzOiAxCiAgICBzZWxlY3RvcjoKICAgICAgYXBwOiBncmFwaGl0ZQogICAgdGVtcGxhdGU6CiAgICAgIG1ldGFkYXRhOgogICAgICAgIGNyZWF0aW9uVGltZXN0YW1wOiBudWxsCiAgICAgICAgbGFiZWxzOgogICAgICAgICAgYXBwOiBncmFwaGl0ZQogICAgICBzcGVjOgogICAgICAgIGNvbnRhaW5lcnM6CiAgICAgICAgLSBpbWFnZTogaG9wc29mdC9ncmFwaGl0ZS1zdGF0c2QKICAgICAgICAgIGltYWdlUHVsbFBvbGljeTogSWZOb3RQcmVzZW50CiAgICAgICAgICBuYW1lOiBncmFwaGl0ZQogICAgICAgICAgcG9ydHM6CiAgICAgICAgICAtIGNvbnRhaW5lclBvcnQ6IDgwCiAgICAgICAgICAgIGhvc3RQb3J0OiA0MDAxCiAgICAgICAgICAgIHByb3RvY29sOiBUQ1AKICAgICAgICAgIC0gY29udGFpbmVyUG9ydDogODEyNQogICAgICAgICAgICBob3N0UG9ydDogODEyNQogICAgICAgICAgICBwcm90b2NvbDogVURQCiAgICAgICAgICByZXNvdXJjZXM6CiAgICAgICAgICAgIGxpbWl0czoKICAgICAgICAgICAgICBjcHU6IDEwMG0KICAgICAgICAgICAgICBtZW1vcnk6IDEwME1pCiAgICAgICAgICB0ZXJtaW5hdGlvbk1lc3NhZ2VQYXRoOiAvZGV2L3Rlcm1pbmF0aW9uLWxvZwogICAgICAgIGRuc1BvbGljeTogQ2x1c3RlckZpcnN0CiAgICAgICAgcmVzdGFydFBvbGljeTogQWx3YXlzCiAgICAgICAgdGVybWluYXRpb25HcmFjZVBlcmlvZFNlY29uZHM6IDMwCiAgc3RhdHVzOgogICAgb2JzZXJ2ZWRHZW5lcmF0aW9uOiAxCiAgICByZXBsaWNhczogMQotIGFwaVZlcnNpb246IHYxCiAga2luZDogUmVwbGljYXRpb25Db250cm9sbGVyCiAgbWV0YWRhdGE6CiAgICBjcmVhdGlvblRpbWVzdGFtcDogMjAxNi0wNS0xN1QxNDo0NToxNloKICAgIGdlbmVyYXRpb246IDEKICAgIGxhYmVsczoKICAgICAgYXBwOiBwb3N0Z3JlcwogICAgbmFtZTogcG9zdGdyZXMKICAgIG5hbWVzcGFjZTogZGVmYXVsdAogICAgcmVzb3VyY2VWZXJzaW9uOiAiMjMxNyIKICAgIHNlbGZMaW5rOiAvYXBpL3YxL25hbWVzcGFjZXMvZGVmYXVsdC9yZXBsaWNhdGlvbmNvbnRyb2xsZXJzL3Bvc3RncmVzCiAgICB1aWQ6IGY4ODZhZmFlLTFjM2QtMTFlNi1hYjk5LTA4MDAyNzY5OTU0YQogIHNwZWM6CiAgICByZXBsaWNhczogMQogICAgc2VsZWN0b3I6CiAgICAgIGFwcDogcG9zdGdyZXMKICAgIHRlbXBsYXRlOgogICAgICBtZXRhZGF0YToKICAgICAgICBjcmVhdGlvblRpbWVzdGFtcDogbnVsbAogICAgICAgIGxhYmVsczoKICAgICAgICAgIGFwcDogcG9zdGdyZXMKICAgICAgc3BlYzoKICAgICAgICBjb250YWluZXJzOgogICAgICAgIC0gaW1hZ2U6IHBvc3RncmVzCiAgICAgICAgICBpbWFnZVB1bGxQb2xpY3k6IElmTm90UHJlc2VudAogICAgICAgICAgbmFtZTogcG9zdGdyZXMKICAgICAgICAgIHBvcnRzOgogICAgICAgICAgLSBjb250YWluZXJQb3J0OiA1NDMyCiAgICAgICAgICAgIGhvc3RQb3J0OiA1NDMyCiAgICAgICAgICAgIHByb3RvY29sOiBUQ1AKICAgICAgICAgIHJlc291cmNlczoKICAgICAgICAgICAgbGltaXRzOgogICAgICAgICAgICAgIGNwdTogMTAwbQogICAgICAgICAgICAgIG1lbW9yeTogNTAwTWkKICAgICAgICAgIHRlcm1pbmF0aW9uTWVzc2FnZVBhdGg6IC9kZXYvdGVybWluYXRpb24tbG9nCiAgICAgICAgZG5zUG9saWN5OiBDbHVzdGVyRmlyc3QKICAgICAgICByZXN0YXJ0UG9saWN5OiBBbHdheXMKICAgICAgICB0ZXJtaW5hdGlvbkdyYWNlUGVyaW9kU2Vjb25kczogMzAKICBzdGF0dXM6CiAgICBvYnNlcnZlZEdlbmVyYXRpb246IDEKICAgIHJlcGxpY2FzOiAxCi0gYXBpVmVyc2lvbjogdjEKICBraW5kOiBSZXBsaWNhdGlvbkNvbnRyb2xsZXIKICBtZXRhZGF0YToKICAgIGNyZWF0aW9uVGltZXN0YW1wOiAyMDE2LTA1LTE3VDE0OjQ1OjM1WgogICAgZ2VuZXJhdGlvbjogMQogICAgbGFiZWxzOgogICAgICBhcHA6IHJhYmJpdG1xCiAgICBuYW1lOiByYWJiaXRtcQogICAgbmFtZXNwYWNlOiBkZWZhdWx0CiAgICByZXNvdXJjZVZlcnNpb246ICIyMzQ1IgogICAgc2VsZkxpbms6IC9hcGkvdjEvbmFtZXNwYWNlcy9kZWZhdWx0L3JlcGxpY2F0aW9uY29udHJvbGxlcnMvcmFiYml0bXEKICAgIHVpZDogMDNiYzYwMjItMWMzZS0xMWU2LWFiOTktMDgwMDI3Njk5NTRhCiAgc3BlYzoKICAgIHJlcGxpY2FzOiAxCiAgICBzZWxlY3RvcjoKICAgICAgYXBwOiByYWJiaXRtcQogICAgdGVtcGxhdGU6CiAgICAgIG1ldGFkYXRhOgogICAgICAgIGNyZWF0aW9uVGltZXN0YW1wOiBudWxsCiAgICAgICAgbGFiZWxzOgogICAgICAgICAgYXBwOiByYWJiaXRtcQogICAgICBzcGVjOgogICAgICAgIGNvbnRhaW5lcnM6CiAgICAgICAgLSBpbWFnZTogcmFiYml0bXEKICAgICAgICAgIGltYWdlUHVsbFBvbGljeTogSWZOb3RQcmVzZW50CiAgICAgICAgICBuYW1lOiByYWJiaXRtcQogICAgICAgICAgcG9ydHM6CiAgICAgICAgICAtIGNvbnRhaW5lclBvcnQ6IDU2NzIKICAgICAgICAgICAgaG9zdFBvcnQ6IDU2NzIKICAgICAgICAgICAgcHJvdG9jb2w6IFRDUAogICAgICAgICAgcmVzb3VyY2VzOgogICAgICAgICAgICBsaW1pdHM6CiAgICAgICAgICAgICAgY3B1OiAxMDBtCiAgICAgICAgICAgICAgbWVtb3J5OiAxMDBNaQogICAgICAgICAgdGVybWluYXRpb25NZXNzYWdlUGF0aDogL2Rldi90ZXJtaW5hdGlvbi1sb2cKICAgICAgICBkbnNQb2xpY3k6IENsdXN0ZXJGaXJzdAogICAgICAgIHJlc3RhcnRQb2xpY3k6IEFsd2F5cwogICAgICAgIHRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzOiAzMAogIHN0YXR1czoKICAgIG9ic2VydmVkR2VuZXJhdGlvbjogMQogICAgcmVwbGljYXM6IDEKa2luZDogTGlzdAptZXRhZGF0YToge30K" 116 | }, 117 | { 118 | "Command": [ 119 | "kubectl", 120 | "get", 121 | "svc", 122 | "--all-namespaces" 123 | ], 124 | "ReturnCode": 0, 125 | "Stderr": "", 126 | "Stdout": "TkFNRVNQQUNFICAgICBOQU1FICAgICAgICAgICAgICAgICAgIENMVVNURVJfSVAgICAgICAgRVhURVJOQUxfSVAgICBQT1JUKFMpICAgICAgICAgICAgIFNFTEVDVE9SICAgICAgICAgICAgICAgICAgICAgICBBR0UKZGVmYXVsdCAgICAgICBncmFwaGl0ZSAgICAgICAgICAgICAgIDEwLjI0Ny44OC4xNzMgICAgbm9kZXMgICAgICAgICA0MDAxL1RDUCAgICAgICAgICAgIGFwcD1ncmFwaGl0ZSAgICAgICAgICAgICAgICAgICAyZApkZWZhdWx0ICAgICAgIGt1YmVybmV0ZXMgICAgICAgICAgICAgMTAuMjQ3LjAuMSAgICAgICA8bm9uZT4gICAgICAgIDQ0My9UQ1AgICAgICAgICAgICAgPG5vbmU+ICAgICAgICAgICAgICAgICAgICAgICAgIDJkCmRlZmF1bHQgICAgICAgcG9zdGdyZXMgICAgICAgICAgICAgICAxMC4yNDcuMTA1LjEgICAgIG5vZGVzICAgICAgICAgNTQzMi9UQ1AgICAgICAgICAgICBhcHA9cG9zdGdyZXMgICAgICAgICAgICAgICAgICAgMmQKZGVmYXVsdCAgICAgICByYWJiaXRtcSAgICAgICAgICAgICAgIDEwLjI0Ny4xMzMuMjUwICAgbm9kZXMgICAgICAgICA1NjcyL1RDUCAgICAgICAgICAgIGFwcD1yYWJiaXRtcSAgICAgICAgICAgICAgICAgICAyZAprdWJlLXN5c3RlbSAgIGhlYXBzdGVyICAgICAgICAgICAgICAgMTAuMjQ3LjIwOS42OSAgICA8bm9uZT4gICAgICAgIDgwL1RDUCAgICAgICAgICAgICAgazhzLWFwcD1oZWFwc3RlciAgICAgICAgICAgICAgIDJkCmt1YmUtc3lzdGVtICAga3ViZS1kbnMgICAgICAgICAgICAgICAxMC4yNDcuMC4xMCAgICAgIDxub25lPiAgICAgICAgNTMvVURQLDUzL1RDUCAgICAgICBrOHMtYXBwPWt1YmUtZG5zICAgICAgICAgICAgICAgMmQKa3ViZS1zeXN0ZW0gICBrdWJlcm5ldGVzLWRhc2hib2FyZCAgIDEwLjI0Ny4xOTEuNjYgICAgPG5vbmU+ICAgICAgICA4MC9UQ1AgICAgICAgICAgICAgIGs4cy1hcHA9a3ViZXJuZXRlcy1kYXNoYm9hcmQgICAyZAprdWJlLXN5c3RlbSAgIG1vbml0b3JpbmctZ3JhZmFuYSAgICAgMTAuMjQ3LjIzOC4xNDQgICA8bm9uZT4gICAgICAgIDgwL1RDUCAgICAgICAgICAgICAgazhzLWFwcD1pbmZsdXhHcmFmYW5hICAgICAgICAgIDJkCmt1YmUtc3lzdGVtICAgbW9uaXRvcmluZy1pbmZsdXhkYiAgICAxMC4yNDcuOTQuMjEwICAgIDxub25lPiAgICAgICAgODA4My9UQ1AsODA4Ni9UQ1AgICBrOHMtYXBwPWluZmx1eEdyYWZhbmEgICAgICAgICAgMmQK" 127 | }, 128 | { 129 | "Command": [ 130 | "kubectl", 131 | "get", 132 | "svc", 133 | "-o", 134 | "yaml", 135 | "--namespace", 136 | "default" 137 | ], 138 | "ReturnCode": 0, 139 | "Stderr": "", 140 | "Stdout": "YXBpVmVyc2lvbjogdjEKaXRlbXM6Ci0gYXBpVmVyc2lvbjogdjEKICBraW5kOiBTZXJ2aWNlCiAgbWV0YWRhdGE6CiAgICBjcmVhdGlvblRpbWVzdGFtcDogMjAxNi0wNS0xN1QxNDo0NToyNFoKICAgIGxhYmVsczoKICAgICAgYXBwOiBncmFwaGl0ZQogICAgbmFtZTogZ3JhcGhpdGUKICAgIG5hbWVzcGFjZTogZGVmYXVsdAogICAgcmVzb3VyY2VWZXJzaW9uOiAiMjMzNSIKICAgIHNlbGZMaW5rOiAvYXBpL3YxL25hbWVzcGFjZXMvZGVmYXVsdC9zZXJ2aWNlcy9ncmFwaGl0ZQogICAgdWlkOiBmY2QxNTI3ZC0xYzNkLTExZTYtYWI5OS0wODAwMjc2OTk1NGEKICBzcGVjOgogICAgY2x1c3RlcklQOiAxMC4yNDcuODguMTczCiAgICBwb3J0czoKICAgIC0gbmFtZTogcG9ydDAKICAgICAgbm9kZVBvcnQ6IDMwNTE1CiAgICAgIHBvcnQ6IDQwMDEKICAgICAgcHJvdG9jb2w6IFRDUAogICAgICB0YXJnZXRQb3J0OiA0MDAxCiAgICBzZWxlY3RvcjoKICAgICAgYXBwOiBncmFwaGl0ZQogICAgc2Vzc2lvbkFmZmluaXR5OiBOb25lCiAgICB0eXBlOiBOb2RlUG9ydAogIHN0YXR1czoKICAgIGxvYWRCYWxhbmNlcjoge30KLSBhcGlWZXJzaW9uOiB2MQogIGtpbmQ6IFNlcnZpY2UKICBtZXRhZGF0YToKICAgIGNyZWF0aW9uVGltZXN0YW1wOiAyMDE2LTA1LTE3VDEyOjA2OjI2WgogICAgbGFiZWxzOgogICAgICBjb21wb25lbnQ6IGFwaXNlcnZlcgogICAgICBwcm92aWRlcjoga3ViZXJuZXRlcwogICAgbmFtZToga3ViZXJuZXRlcwogICAgbmFtZXNwYWNlOiBkZWZhdWx0CiAgICByZXNvdXJjZVZlcnNpb246ICI3IgogICAgc2VsZkxpbms6IC9hcGkvdjEvbmFtZXNwYWNlcy9kZWZhdWx0L3NlcnZpY2VzL2t1YmVybmV0ZXMKICAgIHVpZDogYzgzMmFmNjAtMWMyNy0xMWU2LWFiOTktMDgwMDI3Njk5NTRhCiAgc3BlYzoKICAgIGNsdXN0ZXJJUDogMTAuMjQ3LjAuMQogICAgcG9ydHM6CiAgICAtIG5hbWU6IGh0dHBzCiAgICAgIHBvcnQ6IDQ0MwogICAgICBwcm90b2NvbDogVENQCiAgICAgIHRhcmdldFBvcnQ6IDQ0MwogICAgc2Vzc2lvbkFmZmluaXR5OiBOb25lCiAgICB0eXBlOiBDbHVzdGVySVAKICBzdGF0dXM6CiAgICBsb2FkQmFsYW5jZXI6IHt9Ci0gYXBpVmVyc2lvbjogdjEKICBraW5kOiBTZXJ2aWNlCiAgbWV0YWRhdGE6CiAgICBjcmVhdGlvblRpbWVzdGFtcDogMjAxNi0wNS0xN1QxNDo0NToxN1oKICAgIGxhYmVsczoKICAgICAgYXBwOiBwb3N0Z3JlcwogICAgbmFtZTogcG9zdGdyZXMKICAgIG5hbWVzcGFjZTogZGVmYXVsdAogICAgcmVzb3VyY2VWZXJzaW9uOiAiMjMyMSIKICAgIHNlbGZMaW5rOiAvYXBpL3YxL25hbWVzcGFjZXMvZGVmYXVsdC9zZXJ2aWNlcy9wb3N0Z3JlcwogICAgdWlkOiBmODhlOGM0YS0xYzNkLTExZTYtYWI5OS0wODAwMjc2OTk1NGEKICBzcGVjOgogICAgY2x1c3RlcklQOiAxMC4yNDcuMTA1LjEKICAgIHBvcnRzOgogICAgLSBuYW1lOiBwb3J0MAogICAgICBub2RlUG9ydDogMzAxMTYKICAgICAgcG9ydDogNTQzMgogICAgICBwcm90b2NvbDogVENQCiAgICAgIHRhcmdldFBvcnQ6IDU0MzIKICAgIHNlbGVjdG9yOgogICAgICBhcHA6IHBvc3RncmVzCiAgICBzZXNzaW9uQWZmaW5pdHk6IE5vbmUKICAgIHR5cGU6IE5vZGVQb3J0CiAgc3RhdHVzOgogICAgbG9hZEJhbGFuY2VyOiB7fQotIGFwaVZlcnNpb246IHYxCiAga2luZDogU2VydmljZQogIG1ldGFkYXRhOgogICAgY3JlYXRpb25UaW1lc3RhbXA6IDIwMTYtMDUtMTdUMTQ6NDU6MzVaCiAgICBsYWJlbHM6CiAgICAgIGFwcDogcmFiYml0bXEKICAgIG5hbWU6IHJhYmJpdG1xCiAgICBuYW1lc3BhY2U6IGRlZmF1bHQKICAgIHJlc291cmNlVmVyc2lvbjogIjIzNDkiCiAgICBzZWxmTGluazogL2FwaS92MS9uYW1lc3BhY2VzL2RlZmF1bHQvc2VydmljZXMvcmFiYml0bXEKICAgIHVpZDogMDNjMmFlNDMtMWMzZS0xMWU2LWFiOTktMDgwMDI3Njk5NTRhCiAgc3BlYzoKICAgIGNsdXN0ZXJJUDogMTAuMjQ3LjEzMy4yNTAKICAgIHBvcnRzOgogICAgLSBuYW1lOiBwb3J0MAogICAgICBub2RlUG9ydDogMzEyNTMKICAgICAgcG9ydDogNTY3MgogICAgICBwcm90b2NvbDogVENQCiAgICAgIHRhcmdldFBvcnQ6IDU2NzIKICAgIHNlbGVjdG9yOgogICAgICBhcHA6IHJhYmJpdG1xCiAgICBzZXNzaW9uQWZmaW5pdHk6IE5vbmUKICAgIHR5cGU6IE5vZGVQb3J0CiAgc3RhdHVzOgogICAgbG9hZEJhbGFuY2VyOiB7fQpraW5kOiBMaXN0Cm1ldGFkYXRhOiB7fQo=" 141 | }, 142 | { 143 | "Command": [ 144 | "kubectl", 145 | "logs", 146 | "graphite-dzp4q", 147 | "--namespace", 148 | "default" 149 | ], 150 | "ReturnCode": 0, 151 | "Stderr": "", 152 | "Stdout": "TWF5IDE5IDEyOjE3OjAxIGdyYXBoaXRlLWR6cDRxIC9VU1IvU0JJTi9DUk9OWzcyXTogKHJvb3QpIENNRCAoICAgY2QgLyAmJiBydW4tcGFydHMgLS1yZXBvcnQgL2V0Yy9jcm9uLmhvdXJseSkKTWF5IDE5IDE0OjE3OjAxIGdyYXBoaXRlLWR6cDRxIC9VU1IvU0JJTi9DUk9OWzc1XTogKHJvb3QpIENNRCAoICAgY2QgLyAmJiBydW4tcGFydHMgLS1yZXBvcnQgL2V0Yy9jcm9uLmhvdXJseSkKTWF5IDE5IDE1OjE3OjAxIGdyYXBoaXRlLWR6cDRxIC9VU1IvU0JJTi9DUk9OWzc4XTogKHJvb3QpIENNRCAoICAgY2QgLyAmJiBydW4tcGFydHMgLS1yZXBvcnQgL2V0Yy9jcm9uLmhvdXJseSkK" 153 | } 154 | ] 155 | -------------------------------------------------------------------------------- /tests/test_cache.py: -------------------------------------------------------------------------------- 1 | from hamcrest import * 2 | import unittest 3 | 4 | from kubefuse.cache import ExpiringCache 5 | 6 | class CacheTest(unittest.TestCase): 7 | 8 | def test_cache_get_set(self): 9 | cache = ExpiringCache(expire_in_seconds = 3600) 10 | cache.set("key", "value") 11 | assert_that(cache.get("key"), equal_to("value")) 12 | 13 | def test_cache_expired(self): 14 | cache = ExpiringCache(expire_in_seconds = -10) # expires straight away 15 | cache.set("key", "value") 16 | assert_that(cache.get('key'), equal_to(None)) 17 | 18 | def test_cache_miss(self): 19 | cache = ExpiringCache(expire_in_seconds = 10) 20 | assert_that(cache.get('nonexisting'), equal_to(None)) 21 | 22 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | from hamcrest import * 2 | import yaml 3 | import json 4 | import unittest 5 | 6 | from kubefuse.client import KubernetesClient 7 | 8 | class KubernetesClientTest(unittest.TestCase): 9 | def test_get_namespaces(self): 10 | client = KubernetesClient() 11 | namespaces = client.get_namespaces() 12 | assert_that(namespaces, has_item('default')) 13 | assert_that(namespaces, has_item('kube-system')) 14 | assert_that(len(namespaces), is_(2)) 15 | 16 | def test_get_pods(self): 17 | client = KubernetesClient() 18 | pods = client.get_pods('default') 19 | assert_that(len(pods), is_(3)) 20 | 21 | def test_get_services(self): 22 | client = KubernetesClient() 23 | svc = client.get_services('default') 24 | assert_that(len(svc), is_(4)) 25 | 26 | def test_get_replication_controllers(self): 27 | client = KubernetesClient() 28 | rc = client.get_replication_controllers('default') 29 | assert_that(len(rc), is_(3)) 30 | 31 | def test_get_object_in_yaml_format(self): 32 | client = KubernetesClient() 33 | pods = client.get_pods("default") 34 | pod = client.get_object_in_format('default', 'pod', pods[0], 'yaml') 35 | result = yaml.load(pod) 36 | assert_that(result['metadata']['name'], is_(pods[0])) 37 | 38 | def test_get_object_in_json_format(self): 39 | client = KubernetesClient() 40 | pods = client.get_pods("default") 41 | pod = client.get_object_in_format('default', 'pod', pods[0], 'json') 42 | result = json.loads(pod.decode('utf-8')) 43 | assert_that(result['metadata']['name'], is_(pods[0])) 44 | 45 | def test_describe(self): 46 | client = KubernetesClient() 47 | pods = client.get_pods("default") 48 | describe = client.describe('default', 'pod', pods[0]) 49 | assert_that(str(describe), contains_string(pods[0])) 50 | 51 | def test_logs(self): 52 | client = KubernetesClient() 53 | pods = client.get_pods("default") 54 | describe = client.logs('default', pods[0]) 55 | assert_that(str(describe), contains_string(pods[0])) 56 | -------------------------------------------------------------------------------- /tests/test_filesystem.py: -------------------------------------------------------------------------------- 1 | from hamcrest import * 2 | import stat 3 | import unittest 4 | from fuse import FuseOSError 5 | 6 | from kubefuse.client import KubernetesClient 7 | from kubefuse.path import KubePath 8 | from kubefuse.filesystem import KubeFileSystem 9 | 10 | class KubeFileSystemTest(unittest.TestCase): 11 | def test_getattr_for_namespace(self): 12 | fs = KubeFileSystem(KubernetesClient()) 13 | path = '/default' 14 | attr = fs.getattr(path) 15 | assert_that(attr['st_mode'], is_(stat.S_IFDIR | 0o555)) 16 | assert_that(attr['st_nlink'], is_(2)) 17 | assert_that(attr['st_size'], is_(0)) 18 | # NB. time not tested, but whatever 19 | 20 | def test_getattr_for_resource(self): 21 | fs = KubeFileSystem(KubernetesClient()) 22 | path = '/default/pod' 23 | attr = fs.getattr(path) 24 | assert_that(attr['st_mode'], is_(stat.S_IFDIR | 0o555)) 25 | assert_that(attr['st_nlink'], is_(2)) 26 | assert_that(attr['st_size'], is_(0)) 27 | # NB. time not tested, but whatever 28 | 29 | def test_getattr_for_object(self): 30 | client = KubernetesClient() 31 | pod = client.get_pods()[0] 32 | fs = KubeFileSystem(client) 33 | path = '/default/pod/%s' % pod 34 | attr = fs.getattr(path) 35 | assert_that(attr['st_mode'], is_(stat.S_IFDIR | 0o555)) 36 | assert_that(attr['st_nlink'], is_(2)) 37 | assert_that(attr['st_size'], is_(0)) 38 | # NB. time not tested, but whatever 39 | 40 | def test_getattr_for_action(self): 41 | client = KubernetesClient() 42 | pod = client.get_pods()[0] 43 | fs = KubeFileSystem(client) 44 | path = '/default/pod/%s/describe' % pod 45 | attr = fs.getattr(path) 46 | data = client.describe('default', 'pod', pod) 47 | assert_that(attr['st_mode'], is_(stat.S_IFREG | 0o444)) 48 | assert_that(attr['st_nlink'], is_(1)) 49 | assert_that(attr['st_size'], is_(len(data))) 50 | # NB. time not tested, but whatever 51 | 52 | def test_getattr_size_for_json_action(self): 53 | client = KubernetesClient() 54 | pod = client.get_pods()[0] 55 | fs = KubeFileSystem(client) 56 | path = '/default/pod/%s/json' % pod 57 | attr = fs.getattr(path) 58 | data = client.get_object_in_format('default', 'pod', pod, 'json') 59 | assert_that(attr['st_size'], is_(len(data))) 60 | 61 | def test_getattr_size_for_yaml_action(self): 62 | client = KubernetesClient() 63 | pod = client.get_pods()[0] 64 | fs = KubeFileSystem(client) 65 | path = '/default/pod/%s/yaml' % pod 66 | attr = fs.getattr(path) 67 | data = client.get_object_in_format('default', 'pod', pod, 'yaml') 68 | assert_that(attr['st_size'], is_(len(data))) 69 | 70 | def test_getattr_size_for_describe_action(self): 71 | client = KubernetesClient() 72 | pod = client.get_pods()[0] 73 | fs = KubeFileSystem(client) 74 | path = '/default/pod/%s/logs' % pod 75 | attr = fs.getattr(path) 76 | data = client.logs('default', pod) 77 | assert_that(attr['st_size'], is_(len(data))) 78 | 79 | def test_getattr_for_nonexistent_path(self): 80 | client = KubernetesClient() 81 | fs = KubeFileSystem(client) 82 | path = '/doesnt-exist' 83 | assert_that(calling(lambda: fs.getattr(path)), raises(FuseOSError)) 84 | 85 | def test_getattr_for_truncated_file(self): 86 | client = KubernetesClient() 87 | pod = client.get_pods()[0] 88 | fs = KubeFileSystem(client) 89 | path = '/default/pod/%s/json' % pod 90 | fs.truncate(path, 0) 91 | attr = fs.getattr(path) 92 | assert_that(attr['st_size'], is_(0)) 93 | 94 | def test_list_files_for_root(self): 95 | client = KubernetesClient() 96 | fs = KubeFileSystem(client) 97 | path = '/' 98 | files = fs.list_files(path) 99 | namespaces = client.get_namespaces() 100 | assert_that(files, contains(*namespaces)) 101 | assert_that(len(files), is_(len(namespaces))) 102 | 103 | def test_list_files_for_namespace(self): 104 | client = KubernetesClient() 105 | fs = KubeFileSystem(client) 106 | path = '/default' 107 | files = fs.list_files(path) 108 | assert_that(files, contains_inanyorder('pod', 'svc', 'rc', 'deployments', 'nodes', 'events', 109 | 'limits', 'pv', 'pvc', 'quota', 'endpoints', 'serviceaccounts', 'jobs', 'replicasets', 110 | 'configmaps', 'secrets', 'componentstatuses', 'daemonsets', 'horizontalpodautoscalers', 'ingress')) 111 | 112 | def test_list_files_for_resource(self): 113 | client = KubernetesClient() 114 | fs = KubeFileSystem(client) 115 | path = '/default/pod' 116 | files = fs.list_files(path) 117 | pods = client.get_pods() 118 | assert_that(files, contains(*pods)) 119 | 120 | def test_list_files_for_pod(self): 121 | client = KubernetesClient() 122 | fs = KubeFileSystem(client) 123 | pod = client.get_pods()[0] 124 | path = '/default/pod/%s' % pod 125 | files = fs.list_files(path) 126 | assert_that(files, has_items('describe', 'logs', 'json', 'yaml')) 127 | assert_that(len(files), is_(4)) 128 | 129 | def test_list_files_for_rc(self): 130 | client = KubernetesClient() 131 | fs = KubeFileSystem(client) 132 | rc = client.get_replication_controllers()[0] 133 | path = '/default/rc/%s' % rc 134 | files = fs.list_files(path) 135 | assert_that(files, has_items('describe', 'json', 'yaml')) 136 | assert_that(len(files), is_(3)) 137 | 138 | def test_list_files_for_file_throws_exception(self): 139 | client = KubernetesClient() 140 | fs = KubeFileSystem(client) 141 | pod = client.get_pods()[0] 142 | path = '/default/pod/%s/describe' % pod 143 | assert_that(calling(lambda: fs.list_files(path)), raises(FuseOSError)) 144 | 145 | def test_list_files_for_nonexistent_path(self): 146 | client = KubernetesClient() 147 | fs = KubeFileSystem(client) 148 | path = '/doesnt-exist' 149 | assert_that(calling(lambda: fs.list_files(path)), raises(FuseOSError)) 150 | 151 | def test_read_describe(self): 152 | client = KubernetesClient() 153 | fs = KubeFileSystem(client) 154 | pod = client.get_pods()[0] 155 | path = '/default/pod/%s/describe' % pod 156 | data = fs.read(path, 50000, 0) 157 | assert_that(data, equal_to(client.describe('default', 'pod', pod))) 158 | 159 | def test_read_logs(self): 160 | client = KubernetesClient() 161 | fs = KubeFileSystem(client) 162 | pod = client.get_pods()[0] 163 | path = '/default/pod/%s/logs' % pod 164 | data = fs.read(path, 50000, 0) 165 | assert_that(data, equal_to(client.logs('default', pod))) 166 | 167 | def test_read_json(self): 168 | client = KubernetesClient() 169 | fs = KubeFileSystem(client) 170 | pod = client.get_pods()[0] 171 | path = '/default/pod/%s/json' % pod 172 | data = fs.read(path, 50000, 0) 173 | assert_that(data, equal_to(client.get_object_in_format('default', 'pod', pod, 'json'))) 174 | 175 | def test_read_yaml(self): 176 | client = KubernetesClient() 177 | fs = KubeFileSystem(client) 178 | pod = client.get_pods()[0] 179 | path = '/default/pod/%s/yaml' % pod 180 | data = fs.read(path, 50000, 0) 181 | assert_that(data, equal_to(client.get_object_in_format('default', 'pod', pod, 'yaml'))) 182 | 183 | def test_read_length(self): 184 | client = KubernetesClient() 185 | fs = KubeFileSystem(client) 186 | pod = client.get_pods()[0] 187 | path = '/default/pod/%s/yaml' % pod 188 | data = fs.read(path, 10, 0) 189 | ref = client.get_object_in_format('default', 'pod', pod, 'yaml') 190 | assert_that(data, equal_to(ref[:10])) 191 | 192 | def test_read_offset(self): 193 | client = KubernetesClient() 194 | fs = KubeFileSystem(client) 195 | pod = client.get_pods()[0] 196 | path = '/default/pod/%s/yaml' % pod 197 | data = fs.read(path, 10, 5) 198 | ref = client.get_object_in_format('default', 'pod', pod, 'yaml') 199 | assert_that(data, equal_to(ref[5:15])) 200 | assert_that(len(data), is_(10)) 201 | 202 | def test_truncate_and_write(self): 203 | client = KubernetesClient() 204 | fs = KubeFileSystem(client) 205 | pod = client.get_pods()[0] 206 | path = '/default/pod/%s/yaml' % pod 207 | fs.truncate(path, 0) 208 | fs.write(path, 'test', 0) 209 | fs.write(path, 'write', 4) 210 | fs.sync(path, dry_run=True) 211 | data = fs.read(path, 1000, 0) 212 | assert_that(data, is_(b'testwrite')) 213 | 214 | -------------------------------------------------------------------------------- /tests/test_path.py: -------------------------------------------------------------------------------- 1 | from hamcrest import * 2 | import unittest 3 | 4 | from kubefuse.client import KubernetesClient 5 | from kubefuse.path import KubePath 6 | 7 | class KubePathTest(unittest.TestCase): 8 | def test_parse_path_namespace(self): 9 | kp = KubePath() 10 | kp.parse_path('/default') 11 | assert_that(kp.namespace, is_('default')) 12 | assert_that(kp.resource_type, is_(None)) 13 | assert_that(kp.object_id, is_(None)) 14 | assert_that(kp.action, is_(None)) 15 | 16 | def test_parse_path_resource_type(self): 17 | kp = KubePath() 18 | kp.parse_path('/default/pod') 19 | assert_that(kp.namespace, is_('default')) 20 | assert_that(kp.resource_type, is_('pod')) 21 | assert_that(kp.object_id, is_(None)) 22 | assert_that(kp.action, is_(None)) 23 | 24 | def test_parse_path_object_id(self): 25 | kp = KubePath() 26 | kp.parse_path('/default/pod/pod-123') 27 | assert_that(kp.namespace, is_('default')) 28 | assert_that(kp.resource_type, is_('pod')) 29 | assert_that(kp.object_id, is_('pod-123')) 30 | assert_that(kp.action, is_(None)) 31 | 32 | def test_parse_path_action(self): 33 | kp = KubePath() 34 | kp.parse_path('/default/pod/pod-123/describe') 35 | assert_that(kp.namespace, is_('default')) 36 | assert_that(kp.resource_type, is_('pod')) 37 | assert_that(kp.object_id, is_('pod-123')) 38 | assert_that(kp.action, is_('describe')) 39 | 40 | def test_path_exists_pod(self): 41 | client = KubernetesClient() 42 | pods = client.get_pods() 43 | pod1 = pods[0] 44 | kp = KubePath() 45 | kp.parse_path('/default/pod/%s/describe' % pod1) 46 | assert_that(kp.exists(client), is_(True)) 47 | 48 | def test_path_exists_svc(self): 49 | client = KubernetesClient() 50 | svc = client.get_services() 51 | svc1 = svc[0] 52 | kp = KubePath() 53 | kp.parse_path('/default/svc/%s/describe' % svc1) 54 | assert_that(kp.exists(client), is_(True)) 55 | 56 | def test_path_exists_rc(self): 57 | client = KubernetesClient() 58 | rc = client.get_replication_controllers() 59 | rc1 = rc[0] 60 | kp = KubePath() 61 | kp.parse_path('/default/rc/%s/describe' % rc1) 62 | assert_that(kp.exists(client), is_(True)) 63 | 64 | def test_path_exists_invalid_namespace(self): 65 | client = KubernetesClient() 66 | kp = KubePath() 67 | kp.parse_path('/unknown-namespace') 68 | assert_that(kp.exists(client), is_(False)) 69 | 70 | def test_path_exists_invalid_resource(self): 71 | client = KubernetesClient() 72 | kp = KubePath() 73 | kp.parse_path('/default/mylovelyresource') 74 | assert_that(kp.exists(client), is_(False)) 75 | 76 | def test_path_exists_invalid_object_id(self): 77 | client = KubernetesClient() 78 | kp = KubePath() 79 | kp.parse_path('/default/pod/invalid-id') 80 | assert_that(kp.exists(client), is_(False)) 81 | 82 | def test_path_exists_invalid_action(self): 83 | client = KubernetesClient() 84 | pods = client.get_pods() 85 | pod1 = pods[0] 86 | kp = KubePath() 87 | kp.parse_path('/default/pod/%s/invalid-action' % pod1) 88 | assert_that(kp.exists(client), is_(False)) 89 | --------------------------------------------------------------------------------