├── .gitignore
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── cache_access.go
├── cache_impl.go
├── container.go
├── daemonset.go
├── deployment.go
├── elf_build.sh
├── get_test.go
├── gobuild.sh
├── k8sdeploy.yaml
├── kubeaccess.go
├── kubeiql.go
├── label.go
├── metadata.go
├── owner.go
├── pod.go
├── replicaset.go
├── resource.go
├── server.go
├── service.go
├── statefulset.go
├── testdata
├── daemonset.json
├── deployment.json
├── pod1.json
├── pod2.json
├── pod3.json
├── replicaset.json
├── service.json
└── statefulset.json
├── testing.go
├── util.go
├── vendor
└── vendor.json
├── volume.go
└── watchers.go
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | coverage.*
3 | vendor/github.com/*
4 | vendor/golang.org/*
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribute to Kubeiql
2 | Kubeiql is maintained by Yipee.io, a CA, Inc. Acclerator project. External contributions are welcome and much appreciated. Just follow these easy steps to contribute.
3 |
4 | ## Code Standard and Guideline
5 | For consistency, we ask that you adhere to some basic code guidelines when contributing to the Kubeiql. Please run [gofmt](https://golang.org/cmd/gofmt/) on your code before submitting a pull request.
6 |
7 | ## Our Development Process
8 |
9 | ### Pull Requests
10 | We welcome and encourage pull requests. When we get a pull request, it is peer reviewed and sent to QA for integration and smoke testing. Next, a core team member signs off and performs the pull request merge. We'll provide updates and feedback throughout the process to keep you informed.
11 |
12 | Follow these steps for pull requests:
13 |
14 | 1. Fork the repo and create your branch from `master`.
15 | 1. For any new code, add unit tests.
16 | 1. Verify that the test suite passes.
17 | 1. Verify that your code follows the Code Standard Guideline
18 | 1. If you haven't already, complete the [Contributor License Agreement ("CLA")][cla].
19 |
20 | ### Contributor License Agreement ("CLA")
21 | To get started, sign the Contributor License Agreement.
22 |
23 | ## Bugs
24 | We work hard to avoid them, but they still happen. We use GitHub issues to track bugs and ongoing work for Kubeiql.
25 |
26 | ### Known Issues
27 | We also use GitHub issues for updates to known issues, including alerts when fixes are in progress.
28 |
29 | ### Reporting New Issues
30 | Before filing a new issue, check Known Issues to see if your problem already exists. When reporting a new issue, provide as much detail as possible. The more information, the easier it is to debug and the faster you'll get a fix.
31 |
32 | **Tips:**
33 |
34 | * A description. What did you expect to happen? What actually happened? Why do you think the behavior was incorrect?
35 | * What Kubernetes version are you running? Have you seen the same issue/behavior on other versions too?
36 | * Anything else that seems relevant.
37 |
38 | ## License
39 | By contributing to kubeiql, you agree that your contributions will be licensed under its [license][license].
40 |
41 | [cla]: https://www.clahub.com/agreements/yipeeio/kubeiql
42 | [license]: /LICENSE
43 |
44 | ---
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.8
2 | USER 496
3 | ADD --chown=496:496 ./kubeiql.elf /usr/local/bin/kubeiql
4 | #ADD --chown=496:496 https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VSN:-v1.11.2}/bin/linux/amd64/kubectl /usr/local/bin/kubectl
5 | #RUN chmod 777 /usr/local/bin/kubectl
6 | EXPOSE 8128
7 |
8 | CMD ["/usr/local/bin/kubeiql"]
9 |
10 |
11 |
--------------------------------------------------------------------------------
/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 [yyyy] [name of copyright owner]
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Repository Status
2 | **This repository exists here for historical purposes. The active repo is now hosted at: https://github.com/yipeeio/kubeiql**
3 | # Kubeiql
4 | A GraphQL interface for Kubernetes.
5 |
6 | The goal of this project is to provide an alternative GraphQL
7 | interface to a Kubernetes cluster. It is not intended to entirely replace the
8 | ReST APIs as some of them (particularly the watch APIs) don't map well
9 | onto GraphQL.
10 |
11 | Yipee.io is a CA, Inc. Accelerator project.
12 |
13 | ## Current Status
14 |
15 | pre-alpha
16 |
17 | * Queries are currently supported against Pods, Deployments,
18 | ReplicaSets, StatefulSets, and DaemonSets.
19 | * No mutations are yet implemented.
20 | * The retrieval of data from the cluster is accomplished via watchers
21 | on the kubernetes API. By default, we expect to access the API at
22 | localhost port 8080 (run "kubectl proxy --port=8080")
23 | * to access a kubernetes API without using the proxy, you can set
24 | environment variables:
25 | * API_HOST: host/port of API, e.g., https://kubernetes.default.svc
26 | from inside a cluster
27 | * API_SECRET_PATH: directory containing files 'ca.crt' and
28 | 'token', e.g. /var/run/secrets/kubernetes.io/serviceaccount from
29 | a pod inside a cluster
30 | * Tests are lacking
31 | * See https://hub.docker.com/r/yipeeio/kubeiql/ for a docker image
32 |
33 |
34 | ## Getting Started
35 | To experiment with the API:
36 |
37 | 1. Download the code
38 | 2. Type sh gobuild.sh
39 | 3. Start a proxy for the kubernetes API on port 8080 (e.g., kubectl
40 | proxy --port=8080)
41 | 4. Run ./kubeiql
42 |
43 | The server runs at port 8128. You can use curl to play with it as
44 | shown in the examples below via the /query endpoint, or point your
45 | browser at 'localhost:8128/' and experiment with the GraphiQL tool
46 | (much more user-friendly).
47 |
48 | ## Build an image
49 | If you're running on a non-linux machine, use the _elf-build.sh_
50 | script to build an image suitable for use in a docker container. Then
51 | just build in the usual way:
52 | ```
53 | docker build -t your-image-name .
54 | ```
55 | ## Running inside a Kubernetes cluster
56 | To run kubeiql inside a cluster, simply apply the _k8sdeploy.yaml_
57 | file:
58 | ```
59 | kubectl apply -f k8sdeploy.yaml
60 | ```
61 |
62 | ## Examples
63 |
64 | The query:
65 |
66 | ``` json
67 | {
68 | daemonSetByName(namespace: "kube-system", name: "kube-proxy") {
69 | metadata {name namespace labels {name value}}
70 | }
71 | }
72 | ```
73 |
74 |
75 | curl -X POST -H"Content-Type: application/json"
76 | http://localhost:8128/query -d
77 | '{ "query": "{daemonSetByName(namespace: \"kube-system\", name: \"kube-proxy\") { metadata {name namespace labels {name value}} pods {metadata {name}}}}"}'
78 |
79 |
80 |
81 | returns:
82 |
83 | ```json
84 | {
85 | "data": {
86 | "daemonSetByName": {
87 | "metadata": {
88 | "name": "kube-proxy",
89 | "namespace": "kube-system",
90 | "labels": [
91 | {
92 | "name": "k8s-app",
93 | "value": "kube-proxy"
94 | }
95 | ]
96 | },
97 | "pods": [
98 | {
99 | "metadata": {
100 | "name": "kube-proxy-7vhx5"
101 | }
102 | }
103 | ]
104 | }
105 | }
106 | }
107 |
108 | ```
109 |
110 | and the query:
111 |
112 | ``` json
113 | {
114 | allPods() {
115 | owner {kind metadata {name}}
116 | rootOwner { kind metadata { name namespace }
117 | ... on StatefulSet {
118 | metadata { name }
119 | }
120 | ... on Deployment {
121 | replicaSets {
122 | metadata { name }
123 | pods { metadata { name } } }
124 | }
125 | }
126 | }
127 | }
128 | ```
129 |
130 |
131 | curl -X POST -H"Content-Type: application/json"
132 | http://localhost:8128/query -d '{"query": "{allPods() {owner {kind
133 | metadata {name}} rootOwner { kind metadata { name namespace } ... on
134 | StatefulSet { metadata { name } } ... on Deployment { replicaSets {
135 | metadata { name } pods { metadata { name } } } } } } }" }'
136 |
137 |
138 |
139 | returns:
140 |
141 | ```json
142 | {
143 | "data": {
144 | "allPods": [
145 | {
146 | "owner": {
147 | "kind": "ReplicaSet",
148 | "metadata": {
149 | "name": "backend-549447ccf"
150 | }
151 | },
152 | "rootOwner": {
153 | "kind": "Deployment",
154 | "metadata": {
155 | "name": "backend",
156 | "namespace": "default"
157 | },
158 | "replicaSets": [
159 | {
160 | "metadata": {
161 | "name": "backend-549447ccf"
162 | },
163 | "pods": [
164 | {
165 | "metadata": {
166 | "name": "backend-549447ccf-4zphf"
167 | }
168 | },
169 | {
170 | "metadata": {
171 | "name": "clunky-sabertooth-joomla-5d4ddc985d-fpddz"
172 | }
173 | },
174 | {
175 | "metadata": {
176 | "name": "clunky-sabertooth-mariadb-0"
177 | }
178 | }
179 | // ...
180 | ]
181 | }
182 | ]
183 | }
184 | },
185 | {
186 | "owner": {
187 | "kind": "ReplicaSet",
188 | "metadata": {
189 | "name": "clunky-sabertooth-joomla-5d4ddc985d"
190 | }
191 | },
192 | "rootOwner": {
193 | "kind": "Deployment",
194 | "metadata": {
195 | "name": "clunky-sabertooth-joomla",
196 | "namespace": "default"
197 | },
198 | "replicaSets": [
199 | {
200 | "metadata": {
201 | "name": "backend-549447ccf"
202 | },
203 | "pods": [
204 | {
205 | "metadata": {
206 | "name": "backend-549447ccf-4zphf"
207 | }
208 | },
209 | {
210 | "metadata": {
211 | "name": "clunky-sabertooth-joomla-5d4ddc985d-fpddz"
212 | }
213 | }
214 | //...
215 | ]
216 | }
217 | ]
218 | }
219 | },
220 | {
221 | "owner": {
222 | "kind": "StatefulSet",
223 | "metadata": {
224 | "name": "clunky-sabertooth-mariadb"
225 | }
226 | },
227 | "rootOwner": {
228 | "kind": "StatefulSet",
229 | "metadata": {
230 | "name": "clunky-sabertooth-mariadb",
231 | "namespace": "default"
232 | }
233 | }
234 | },
235 | {
236 | "owner": {
237 | "kind": "ReplicaSet",
238 | "metadata": {
239 | "name": "ui-9c6c8d79"
240 | }
241 | },
242 | "rootOwner": {
243 | "kind": "Deployment",
244 | "metadata": {
245 | "name": "ui",
246 | "namespace": "default"
247 | },
248 | "replicaSets": [
249 | {
250 | "metadata": {
251 | "name": "backend-549447ccf"
252 | },
253 | "pods": [
254 | {
255 | "metadata": {
256 | "name": "backend-549447ccf-4zphf"
257 | }
258 | },
259 | {
260 | "metadata": {
261 | "name": "clunky-sabertooth-joomla-5d4ddc985d-fpddz"
262 | }
263 | }
264 | // ...
265 | ]
266 | }
267 | ]
268 | }
269 | },
270 | {
271 | "owner": {
272 | "kind": "ReplicaSet",
273 | "metadata": {
274 | "name": "ui-9c6c8d79"
275 | }
276 | },
277 | "rootOwner": {
278 | "kind": "Deployment",
279 | "metadata": {
280 | "name": "ui",
281 | "namespace": "default"
282 | },
283 | "replicaSets": [
284 | {
285 | "metadata": {
286 | "name": "backend-549447ccf"
287 | },
288 | "pods": [
289 | {
290 | "metadata": {
291 | "name": "backend-549447ccf-4zphf"
292 | }
293 | },
294 | {
295 | "metadata": {
296 | "name": "clunky-sabertooth-joomla-5d4ddc985d-fpddz"
297 | }
298 | }
299 | // ...
300 | ]
301 | }
302 | ]
303 | }
304 | },
305 | {
306 | "owner": {
307 | "kind": "Pod",
308 | "metadata": {
309 | "name": "etcd-minikube"
310 | }
311 | },
312 | "rootOwner": {
313 | "kind": "Pod",
314 | "metadata": {
315 | "name": "etcd-minikube",
316 | "namespace": "kube-system"
317 | }
318 | }
319 | },
320 | {
321 | "owner": {
322 | "kind": "Pod",
323 | "metadata": {
324 | "name": "kube-addon-manager-minikube"
325 | }
326 | },
327 | "rootOwner": {
328 | "kind": "Pod",
329 | "metadata": {
330 | "name": "kube-addon-manager-minikube",
331 | "namespace": "kube-system"
332 | }
333 | }
334 | },
335 | {
336 | "owner": {
337 | "kind": "Pod",
338 | "metadata": {
339 | "name": "kube-apiserver-minikube"
340 | }
341 | },
342 | "rootOwner": {
343 | "kind": "Pod",
344 | "metadata": {
345 | "name": "kube-apiserver-minikube",
346 | "namespace": "kube-system"
347 | }
348 | }
349 | },
350 | {
351 | "owner": {
352 | "kind": "Pod",
353 | "metadata": {
354 | "name": "kube-controller-manager-minikube"
355 | }
356 | },
357 | "rootOwner": {
358 | "kind": "Pod",
359 | "metadata": {
360 | "name": "kube-controller-manager-minikube",
361 | "namespace": "kube-system"
362 | }
363 | }
364 | },
365 | {
366 | "owner": {
367 | "kind": "ReplicaSet",
368 | "metadata": {
369 | "name": "kube-dns-86f4d74b45"
370 | }
371 | },
372 | "rootOwner": {
373 | "kind": "Deployment",
374 | "metadata": {
375 | "name": "kube-dns",
376 | "namespace": "kube-system"
377 | },
378 | "replicaSets": [
379 | {
380 | "metadata": {
381 | "name": "backend-549447ccf"
382 | },
383 | "pods": [
384 | {
385 | "metadata": {
386 | "name": "backend-549447ccf-4zphf"
387 | }
388 | },
389 | {
390 | "metadata": {
391 | "name": "clunky-sabertooth-joomla-5d4ddc985d-fpddz"
392 | }
393 | }
394 | // ...
395 | ]
396 | }
397 | ]
398 | }
399 | },
400 | {
401 | "owner": {
402 | "kind": "DaemonSet",
403 | "metadata": {
404 | "name": "kube-proxy"
405 | }
406 | },
407 | "rootOwner": {
408 | "kind": "DaemonSet",
409 | "metadata": {
410 | "name": "kube-proxy",
411 | "namespace": "kube-system"
412 | }
413 | }
414 | },
415 | {
416 | "owner": {
417 | "kind": "Pod",
418 | "metadata": {
419 | "name": "kube-scheduler-minikube"
420 | }
421 | },
422 | "rootOwner": {
423 | "kind": "Pod",
424 | "metadata": {
425 | "name": "kube-scheduler-minikube",
426 | "namespace": "kube-system"
427 | }
428 | }
429 | }
430 | // ...
431 | ]
432 | }
433 | }
434 |
435 | ```
436 | ## License
437 |
438 | The work done has been licensed under Apache License 2.0. The license file can be found [here](LICENSE). You can find
439 | out more about the license at [www.apache.org/licenses/LICENSE-2.0](//www.apache.org/licenses/LICENSE-2.0).
440 |
441 | ## Questions?
442 |
443 | Feel free to [contact us](mailto:support@yipee.io).
444 |
--------------------------------------------------------------------------------
/cache_access.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | var client CacheClient
18 |
19 | type CacheOp func() interface{}
20 |
21 | type CacheRequest struct {
22 | operation CacheOp
23 | replyChan chan interface{}
24 | }
25 |
26 | type clientif interface {
27 | Lookup(key string) interface{}
28 | Remove(obj *JsonObject)
29 | Add(obj *JsonObject)
30 | }
31 |
32 | type CacheClient struct {
33 | serverMbox chan<- *CacheRequest
34 | }
35 |
36 | func initCacheClient(serverMbox chan *CacheRequest) {
37 | client = CacheClient{serverMbox}
38 | }
39 |
40 | func GetCache() *CacheClient {
41 | return &client
42 | }
43 |
44 | func (client *CacheClient) Lookup(key string) interface{} {
45 | replyChan := make(chan interface{})
46 | req := &CacheRequest{
47 | func() interface{} {
48 | return cacheLookup(key)
49 | }, replyChan,
50 | }
51 | client.serverMbox <- req
52 | retval := <-replyChan
53 | return retval
54 | }
55 |
56 | func (client *CacheClient) Remove(obj *JsonObject) {
57 | replyChan := make(chan interface{})
58 | req := &CacheRequest{
59 | func() interface{} {
60 | removeFromCache(obj)
61 | return true
62 | }, replyChan,
63 | }
64 | client.serverMbox <- req
65 | if retval := <-replyChan; retval != true {
66 | panic("bad return from cache Remove")
67 | }
68 | }
69 |
70 | func (client *CacheClient) Add(obj *JsonObject) {
71 | replyChan := make(chan interface{})
72 | req := &CacheRequest{
73 | func() interface{} {
74 | addToCache(obj)
75 | return true
76 | }, replyChan,
77 | }
78 | client.serverMbox <- req
79 | if retval := <-replyChan; retval != true {
80 | panic("bad return from cache Add")
81 | }
82 | }
83 |
84 | // compilation error if we don't implement the i/f properly
85 | var _ clientif = (*CacheClient)(nil)
86 |
87 | func cacheKey(kind, namespace, name string) string {
88 | return kind + "#" + namespace + "#" + name
89 | }
90 |
91 | func nsCacheKey(kind, namespace string) string {
92 | return kind + "#" + namespace
93 | }
94 |
--------------------------------------------------------------------------------
/cache_impl.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "fmt"
19 | )
20 |
21 | // N.B.: this is the cache implementation whose functions are
22 | // not intended to be called directly.
23 | // The intended cache interface
24 | // is in cache_access.go: namely "Lookup", "Add", "Remove",
25 | // along with the key-building functions.
26 | // All access to the cache is intended to be via the server mailbox.
27 | // That is our serialization mechanism (akin to an erlang gen_server).
28 | //
29 | // (Deliberately resisting the "internal" package goo...)
30 | var cache map[string]interface{}
31 |
32 | func runServer(mbox <-chan *CacheRequest) {
33 | for {
34 | req, ok := <-mbox
35 | if ok {
36 | req.replyChan <- req.operation()
37 | } else {
38 | break
39 | }
40 | }
41 | }
42 |
43 | func initCache() {
44 | cache = make(map[string]interface{})
45 | serverMbox := make(chan *CacheRequest)
46 | go runServer(serverMbox)
47 | initCacheClient(serverMbox)
48 | }
49 |
50 | func findInList(clist []*JsonObject, target *JsonObject) int {
51 | tname := getName(*target)
52 | tns := getNamespace(*target)
53 | tkind := getKind(*target)
54 | for idx, obj := range clist {
55 | name := getName(*obj)
56 | ns := getNamespace(*obj)
57 | kind := getKind(*obj)
58 | if tname == name && tns == ns && tkind == kind {
59 | return idx
60 | }
61 | }
62 | return -1
63 | }
64 |
65 | func deleteFromCacheList(key string, obj *JsonObject) {
66 | if val, ok := cache[key]; ok {
67 | clist := val.([]*JsonObject)
68 | idx := findInList(clist, obj)
69 | if idx > -1 {
70 | cache[key] = append(clist[:idx], clist[idx+1:]...)
71 | }
72 | }
73 | }
74 |
75 | func addToCacheList(key string, obj *JsonObject) {
76 | if val, ok := cache[key]; ok {
77 | clist := val.([]*JsonObject)
78 | idx := findInList(clist, obj)
79 | if idx == -1 {
80 | cache[key] = append(clist, obj)
81 | } else {
82 | clist[idx] = obj
83 | }
84 | } else {
85 | cache[key] = []*JsonObject{obj}
86 | }
87 | }
88 |
89 | func formattedName(obj *JsonObject) string {
90 | return cacheKey(getKind(*obj), getNamespace(*obj), getName(*obj))
91 | }
92 |
93 | func addToCache(obj *JsonObject) {
94 | kind := getKind(*obj)
95 | ns := getNamespace(*obj)
96 | name := getName(*obj)
97 | cacheKey := cacheKey(kind, ns, name)
98 | nsCacheKey := nsCacheKey(kind, ns)
99 | cache[cacheKey] = obj
100 | addToCacheList(nsCacheKey, obj)
101 | addToCacheList(kind, obj)
102 | }
103 |
104 | func removeFromCache(obj *JsonObject) {
105 | kind := getKind(*obj)
106 | ns := getNamespace(*obj)
107 | name := getName(*obj)
108 | cacheKey := cacheKey(kind, ns, name)
109 | nsCacheKey := nsCacheKey(kind, ns)
110 | delete(cache, cacheKey)
111 | deleteFromCacheList(nsCacheKey, obj)
112 | deleteFromCacheList(kind, obj)
113 | }
114 |
115 | func cacheLookup(key string) interface{} {
116 | if val, ok := cache[key]; ok {
117 | if ref, ok := val.(*JsonObject); ok {
118 | return *ref
119 | } else if list, ok := val.([]*JsonObject); ok {
120 | // clone returned slice so that its "shape" can't be changed
121 | // while caller is holding it...
122 | retlist := make([]JsonObject, len(list))
123 | for idx, item := range list {
124 | retlist[idx] = *item
125 | }
126 | return retlist
127 | } else {
128 | panic(fmt.Sprintf("invalid type in cache: %T", val))
129 | }
130 | } else {
131 | return nil
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/container.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | // "fmt"
20 | )
21 |
22 | // Containers do the actual work in Kubernetes
23 |
24 | type container struct {
25 | }
26 |
27 | type containerResolver struct {
28 | ctx context.Context
29 | c container
30 | }
31 |
32 | // Translate unmarshalled json into a deployment object
33 | func mapToContainer(ctx context.Context, jsonObj JsonObject) container {
34 | return container{}
35 | }
36 |
--------------------------------------------------------------------------------
/daemonset.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | // "fmt"
20 | "sort"
21 | "strings"
22 | )
23 |
24 | // DaemonSets place a pod on each server
25 | type daemonSet struct {
26 | Metadata metadata
27 | Owner resource
28 | RootOwner resource
29 | Pods *[]pod
30 | }
31 |
32 | type daemonSetResolver struct {
33 | ctx context.Context
34 | d daemonSet
35 | }
36 |
37 | // Translate unmarshalled json into a deployment object
38 | func mapToDaemonSet(
39 | ctx context.Context,
40 | jsonObj JsonObject) daemonSet {
41 | placeholder := &ownerRef{ctx, jsonObj, nil}
42 | owner := placeholder
43 | rootOwner := placeholder
44 | meta :=
45 | mapToMetadata(ctx, getNamespace(jsonObj), mapItem(jsonObj, "metadata"))
46 | return daemonSet{meta, owner, rootOwner, nil}
47 | }
48 |
49 | // DaemonSets have pods as children
50 | func getDaemonSetPods(ctx context.Context, d daemonSet) *[]pod {
51 | dsName := *d.Metadata.Name
52 | dsNamePrefix := dsName + "-"
53 | dsNamespace := *d.Metadata.Namespace
54 |
55 | pset := getAllK8sObjsOfKindInNamespace(
56 | ctx,
57 | PodKind,
58 | dsNamespace,
59 | func(jobj JsonObject) bool {
60 | return (strings.HasPrefix(getName(jobj), dsNamePrefix) &&
61 | hasMatchingOwner(jobj, dsName, DaemonSetKind))
62 | })
63 |
64 | results := make([]pod, len(pset))
65 |
66 | for idx, p := range pset {
67 | pr := p.(*podResolver)
68 | results[idx] = pr.p
69 | }
70 |
71 | sort.Slice(
72 | results,
73 | func(i, j int) bool {
74 | return *results[i].Metadata.Name < *results[j].Metadata.Name
75 | })
76 |
77 | return &results
78 | }
79 |
80 | // Resource method implementations
81 | func (r *daemonSetResolver) Kind() string {
82 | return DaemonSetKind
83 | }
84 |
85 | func (r *daemonSetResolver) Metadata() metadataResolver {
86 | return metadataResolver{r.ctx, r.d.Metadata}
87 | }
88 |
89 | func (r *daemonSetResolver) Owner() *resourceResolver {
90 | if oref, ok := r.d.Owner.(*ownerRef); ok {
91 | r.d.Owner = getOwner(oref.ctx, oref.ref)
92 | }
93 | return &resourceResolver{r.ctx, r.d.Owner}
94 | }
95 |
96 | func (r *daemonSetResolver) RootOwner() *resourceResolver {
97 | if oref, ok := r.d.Owner.(*ownerRef); ok {
98 | r.d.Owner = getOwner(oref.ctx, oref.ref)
99 | }
100 | return &resourceResolver{r.ctx, r.d.RootOwner}
101 | }
102 |
103 | // Resolve child Pods
104 | func (r *daemonSetResolver) Pods() []*podResolver {
105 | if r.d.Pods == nil {
106 | r.d.Pods = getDaemonSetPods(r.ctx, r.d)
107 | }
108 |
109 | var res []*podResolver
110 | for _, p := range *r.d.Pods {
111 | res = append(res, &podResolver{r.ctx, p})
112 | }
113 | if res == nil {
114 | res = make([]*podResolver, 0)
115 | }
116 | return res
117 | }
118 |
--------------------------------------------------------------------------------
/deployment.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | // "fmt"
20 | "strings"
21 | )
22 |
23 | // Top level Kubernetes replicated controller. Deployments are built out
24 | // of ReplicaSets.
25 | type deployment struct {
26 | Metadata metadata
27 | Spec deploymentSpec
28 | Owner resource
29 | RootOwner resource
30 | ReplicaSets *[]replicaSet
31 | }
32 |
33 | type deploymentSpec struct {
34 | MinReadySeconds int32
35 | Paused bool
36 | ProgressDeadlineSeconds int32
37 | Replicas int32
38 | RevisionHistoryLimit int32
39 | Selector *labelSelector
40 | Strategy *deploymentStrategy
41 | Template podTemplateSpec
42 | }
43 |
44 | type podTemplateSpec struct {
45 | Metadata metadata
46 | Spec podSpec
47 | }
48 |
49 | type labelSelector struct {
50 | MatchExpressions *[]labelSelectorRequirement
51 | MatchLabels *[]label
52 | }
53 |
54 | type labelSelectorRequirement struct {
55 | Key string
56 | Operator string
57 | Values []string
58 | }
59 |
60 | type deploymentStrategy struct {
61 | RollingUpdate *rollingUpdateDeployment
62 | Type *string
63 | }
64 |
65 | type rollingUpdateDeployment struct {
66 | MaxSurgeInt *int32
67 | MaxSurgeString *string
68 | MaxUnavailableInt *int32
69 | MaxUnavailableString *string
70 | }
71 |
72 | type deploymentResolver struct {
73 | ctx context.Context
74 | d deployment
75 | }
76 |
77 | type deploymentSpecResolver struct {
78 | ctx context.Context
79 | d deploymentSpec
80 | }
81 |
82 | type labelSelectorResolver struct {
83 | ctx context.Context
84 | l labelSelector
85 | }
86 |
87 | type deploymentStrategyResolver struct {
88 | ctx context.Context
89 | d *deploymentStrategy
90 | }
91 |
92 | type labelSelectorRequirementResolver struct {
93 | ctx context.Context
94 | l labelSelectorRequirement
95 | }
96 |
97 | type rollingUpdateDeploymentResolver struct {
98 | ctx context.Context
99 | r rollingUpdateDeployment
100 | }
101 |
102 | type podTemplateSpecResolver struct {
103 | ctx context.Context
104 | p podTemplateSpec
105 | }
106 |
107 | // Translate unmarshalled json into a deployment object
108 | func mapToDeployment(
109 | ctx context.Context,
110 | jsonObj JsonObject) deployment {
111 | ns := getNamespace(jsonObj)
112 | return deployment{
113 | mapToMetadata(ctx, ns, mapItem(jsonObj, "metadata")),
114 | extractDeploymentSpec(ctx, ns, mapItem(jsonObj, "spec")),
115 | nil,
116 | nil,
117 | nil}
118 | }
119 |
120 | func extractDeploymentSpec(ctx context.Context, ns string, jsonObj JsonObject) deploymentSpec {
121 | jg := jgetter(jsonObj)
122 | template := mapItem(jsonObj, "template")
123 | return deploymentSpec{
124 | jg.intItemOr("minReadySeconds", 0),
125 | jg.boolItemOr("paused", false),
126 | jg.intItemOr("progressDeadlineSeconds", 600),
127 | jg.intItemOr("replicas", 1),
128 | jg.intItemOr("revisionHistoryLimit", 10),
129 | mapToSelector(jg.objItemOr("selector", nil)),
130 | mapToStrategy(jg.objItemOr("strategy", nil)),
131 | podTemplateSpec{
132 | mapToMetadata(ctx, ns, mapItem(template, "metadata")),
133 | mapToPodSpec(ctx, mapItem(template, "spec"))}}
134 | }
135 |
136 | func mapToSelector(sel *JsonObject) *labelSelector {
137 | if sel == nil {
138 | return nil
139 | }
140 | jg := jgetter(*sel)
141 | exprs := jg.arrayItemOr("matchExpressions", nil)
142 | labels := jg.objItemOr("matchLabels", nil)
143 |
144 | var exprsVal *[]labelSelectorRequirement
145 |
146 | if exprs != nil {
147 | eslice := make([]labelSelectorRequirement, len(*exprs))
148 | exprsVal = &eslice
149 | for idx, lsr := range *exprs {
150 | jg := jgetter(lsr.(JsonObject))
151 | vals := jg.arrayItem("values")
152 | strVals := make([]string, len(vals))
153 | for sidx, sval := range vals {
154 | strVals[sidx] = sval.(string)
155 | }
156 | (*exprsVal)[idx] = labelSelectorRequirement{
157 | jg.stringItem("key"),
158 | jg.stringItem("operator"),
159 | strVals}
160 | }
161 | }
162 |
163 | if labels != nil {
164 | lslice := make([]label, len(*labels))
165 | i := 0
166 | for k, v := range *labels {
167 | lslice[i] = label{k, v.(string)}
168 | i = i + 1
169 | }
170 |
171 | return &labelSelector{exprsVal, &lslice}
172 | }
173 |
174 | empty := make([]label, 0)
175 | return &labelSelector{exprsVal, &empty}
176 | }
177 |
178 | func mapToStrategy(strat *JsonObject) *deploymentStrategy {
179 | if strat == nil {
180 | return nil
181 | }
182 | jg := jgetter(*strat)
183 | sType := jg.stringRefItemOr("type", nil)
184 | var updateItem *JsonObject
185 |
186 | if *sType == "RollingUpdate" {
187 | updateItem = jg.objItemOr("rollingUpdate", nil)
188 | }
189 |
190 | if updateItem == nil {
191 | return &deploymentStrategy{nil, sType}
192 | }
193 |
194 | sval, spresent := (*updateItem)["maxSurge"]
195 | uval, upresent := (*updateItem)["maxUnavailable"]
196 | var ss, su string
197 | var is, iu int32
198 | var ssptr *string = nil
199 | var suptr *string = nil
200 | var isptr *int32 = nil
201 | var iuptr *int32 = nil
202 | defval := "25%"
203 |
204 | if !spresent {
205 | ss = defval
206 | ssptr = &ss
207 | } else if ssval, ok := sval.(string); ok {
208 | ss = ssval
209 | ssptr = &ss
210 | } else {
211 | is = toGQLInt(sval)
212 | isptr = &is
213 | }
214 |
215 | if !upresent {
216 | su = defval
217 | suptr = &su
218 | } else if suval, ok := uval.(string); ok {
219 | su = suval
220 | suptr = &su
221 | } else {
222 | iu = toGQLInt(uval)
223 | iuptr = &iu
224 | }
225 |
226 | return &deploymentStrategy{
227 | &rollingUpdateDeployment{isptr, ssptr, iuptr, suptr},
228 | sType}
229 | }
230 |
231 | // Retrieve the ReplicaSets comprising the deployment
232 | func getReplicaSets(ctx context.Context, d deployment) *[]replicaSet {
233 | depName := *d.Metadata.Name
234 | depNamePrefix := depName + "-"
235 | depNamespace := *d.Metadata.Namespace
236 |
237 | rsets := getAllK8sObjsOfKindInNamespace(
238 | ctx,
239 | "ReplicaSet",
240 | depNamespace,
241 | func(jobj JsonObject) bool {
242 | return (strings.HasPrefix(getName(jobj), depNamePrefix) &&
243 | hasMatchingOwner(jobj, depName, DeploymentKind))
244 | })
245 |
246 | results := make([]replicaSet, len(rsets))
247 |
248 | for idx, rs := range rsets {
249 | rsr := rs.(*replicaSetResolver)
250 | results[idx] = rsr.r
251 | }
252 |
253 | return &results
254 | }
255 |
256 | func (r *labelSelectorResolver) MatchExpressions() *[]labelSelectorRequirementResolver {
257 | if r.l.MatchExpressions == nil {
258 | empty := make([]labelSelectorRequirementResolver, 0)
259 | return &empty
260 | }
261 | resolvers := make([]labelSelectorRequirementResolver,
262 | len(*r.l.MatchExpressions))
263 | for idx, val := range *r.l.MatchExpressions {
264 | resolvers[idx] = labelSelectorRequirementResolver{r.ctx, val}
265 | }
266 | return &resolvers
267 | }
268 |
269 | func (r *labelSelectorResolver) MatchLabels() *[]labelResolver {
270 | if r.l.MatchLabels == nil {
271 | empty := make([]labelResolver, 0)
272 | return &empty
273 | }
274 | resolvers := make([]labelResolver, len(*r.l.MatchLabels))
275 | for idx, val := range *r.l.MatchLabels {
276 | labelVal := val
277 | resolvers[idx] = labelResolver{r.ctx, &labelVal}
278 | }
279 | return &resolvers
280 | }
281 |
282 | func (r labelSelectorRequirementResolver) Key() string {
283 | return r.l.Key
284 | }
285 |
286 | func (r labelSelectorRequirementResolver) Operator() string {
287 | return r.l.Operator
288 | }
289 |
290 | func (r labelSelectorRequirementResolver) Values() []string {
291 | return r.l.Values
292 | }
293 |
294 | // Pod template spec implementations
295 | func (r podTemplateSpecResolver) Metadata() metadataResolver {
296 | return metadataResolver{r.ctx, r.p.Metadata}
297 | }
298 |
299 | func (r podTemplateSpecResolver) Spec() podSpecResolver {
300 | return podSpecResolver{r.ctx, r.p.Spec}
301 | }
302 |
303 | // Resource method implementations
304 | func (r *deploymentResolver) Kind() string {
305 | return DeploymentKind
306 | }
307 |
308 | func (r *deploymentResolver) Metadata() metadataResolver {
309 | return metadataResolver{r.ctx, r.d.Metadata}
310 | }
311 |
312 | func (r *deploymentResolver) Spec() deploymentSpecResolver {
313 | return deploymentSpecResolver{r.ctx, r.d.Spec}
314 | }
315 |
316 | func (r *deploymentResolver) Owner() *resourceResolver {
317 | return &resourceResolver{r.ctx, &deploymentResolver{r.ctx, r.d}}
318 | }
319 |
320 | func (r *deploymentResolver) RootOwner() *resourceResolver {
321 | return &resourceResolver{r.ctx, &deploymentResolver{r.ctx, r.d}}
322 | }
323 |
324 | // Deployment spec implementations
325 | func (r deploymentSpecResolver) MinReadySeconds() int32 {
326 | return r.d.MinReadySeconds
327 | }
328 |
329 | func (r deploymentSpecResolver) Paused() bool {
330 | return r.d.Paused
331 | }
332 |
333 | func (r deploymentSpecResolver) ProgressDeadlineSeconds() int32 {
334 | return r.d.ProgressDeadlineSeconds
335 | }
336 |
337 | func (r deploymentSpecResolver) Replicas() int32 {
338 | return r.d.Replicas
339 | }
340 |
341 | func (r deploymentSpecResolver) RevisionHistoryLimit() int32 {
342 | return r.d.RevisionHistoryLimit
343 | }
344 |
345 | func (r deploymentSpecResolver) Selector() *labelSelectorResolver {
346 | if r.d.Selector != nil {
347 | return &labelSelectorResolver{r.ctx, *r.d.Selector}
348 | }
349 |
350 | return nil
351 | }
352 |
353 | func (r deploymentSpecResolver) Template() podTemplateSpecResolver {
354 | return podTemplateSpecResolver{r.ctx, r.d.Template}
355 | }
356 |
357 | func (r deploymentSpecResolver) Strategy() *deploymentStrategyResolver {
358 | return &deploymentStrategyResolver{r.ctx, r.d.Strategy}
359 | }
360 |
361 | // Resolve child ReplicaSets
362 | func (r *deploymentResolver) ReplicaSets() []*replicaSetResolver {
363 | if r.d.ReplicaSets == nil {
364 | r.d.ReplicaSets = getReplicaSets(r.ctx, r.d)
365 | }
366 |
367 | var res []*replicaSetResolver
368 | for _, rs := range *r.d.ReplicaSets {
369 | res = append(res, &replicaSetResolver{r.ctx, rs})
370 | }
371 | if res == nil {
372 | res = make([]*replicaSetResolver, 0)
373 | }
374 | return res
375 | }
376 |
377 | func (r deploymentStrategyResolver) RollingUpdate() *rollingUpdateDeploymentResolver {
378 | if r.d.RollingUpdate == nil {
379 | return nil
380 | }
381 | return &rollingUpdateDeploymentResolver{r.ctx, *r.d.RollingUpdate}
382 | }
383 |
384 | func (r deploymentStrategyResolver) Type() *string {
385 | val := "RollingUpdate"
386 | if r.d == nil || r.d.Type == nil {
387 | return &val
388 | }
389 | return r.d.Type
390 | }
391 |
392 | func (r rollingUpdateDeploymentResolver) MaxSurgeInt() *int32 {
393 | return r.r.MaxSurgeInt
394 | }
395 |
396 | func (r rollingUpdateDeploymentResolver) MaxUnavailableInt() *int32 {
397 | return r.r.MaxUnavailableInt
398 | }
399 |
400 | func (r rollingUpdateDeploymentResolver) MaxSurgeString() *string {
401 | return r.r.MaxSurgeString
402 | }
403 |
404 | func (r rollingUpdateDeploymentResolver) MaxUnavailableString() *string {
405 | return r.r.MaxUnavailableString
406 | }
407 |
--------------------------------------------------------------------------------
/elf_build.sh:
--------------------------------------------------------------------------------
1 | # Build our executable in a docker container to make sure we get a linux/ELF
2 | # binary. Useful for development on a mac. Not sure about windows...
3 | APP_NAME=kubeiql.elf
4 | docker run --rm -e "GOPATH=/usr" -e "CGO_ENABLED=0" -v "$PWD":/usr/src/${APP_NAME} -w /usr/src/${APP_NAME} golang:1.10 go build
5 |
--------------------------------------------------------------------------------
/get_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | // "fmt"
20 | graphql "github.com/neelance/graphql-go"
21 | "io/ioutil"
22 | "log"
23 | "testing"
24 | )
25 |
26 | var testschema *graphql.Schema = graphql.MustParseSchema(Schema, &Resolver{})
27 |
28 | func simpletest(t *testing.T, query string, result string) {
29 | var stest Test
30 | stest.Schema = testschema
31 | stest.Query = query
32 | stest.ExpectedResult = result
33 | RunTest(t, &stest)
34 | }
35 |
36 | func init() {
37 | cache := make(map[string]interface{})
38 | ctx := context.WithValue(context.Background(), "queryCache", &cache)
39 | setTestContext(&ctx)
40 | for _, fname := range []string{
41 | "deployment.json",
42 | "replicaset.json",
43 | "daemonset.json",
44 | "statefulset.json",
45 | "service.json",
46 | "pod1.json",
47 | "pod2.json",
48 | "pod3.json"} {
49 | addToTestCache(&cache, "testdata/"+fname)
50 | }
51 | }
52 |
53 | func addToTestCache(cacheref *map[string]interface{}, fname string) {
54 | bytes, err := ioutil.ReadFile(fname)
55 | if err != nil {
56 | log.Fatal(err)
57 | }
58 | data := fromJson(bytes).(JsonObject)
59 | cache := GetCache()
60 | cache.Add(&data)
61 | }
62 |
63 | func TestPods(t *testing.T) {
64 | simpletest(
65 | t,
66 | `{
67 | allDeployments() {
68 | metadata {
69 | creationTimestamp
70 | generation
71 | labels { name value }
72 | }
73 | spec {
74 | minReadySeconds
75 | paused
76 | progressDeadlineSeconds
77 | replicas
78 | revisionHistoryLimit
79 | selector {
80 | matchLabels { name value }
81 | matchExpressions {
82 | key
83 | operator
84 | values
85 | }
86 | }
87 | strategy {
88 | type
89 | rollingUpdate {
90 | maxSurgeInt
91 | maxSurgeString
92 | maxUnavailableInt
93 | maxUnavailableString
94 | }
95 | }
96 | template {
97 | metadata {
98 | creationTimestamp
99 | labels { name value }
100 | },
101 | spec {
102 | dnsPolicy
103 | restartPolicy
104 | schedulerName
105 | terminationGracePeriodSeconds
106 | volumes {
107 | name
108 | persistentVolumeClaim { claimName readOnly }
109 | }
110 | }
111 | }
112 | }
113 | replicaSets {
114 | metadata {
115 | name
116 | }
117 | pods {
118 | metadata {
119 | name
120 | namespace
121 | labels {
122 | name
123 | value
124 | }
125 | }
126 | spec {
127 | dnsPolicy
128 | nodeName
129 | restartPolicy
130 | schedulerName
131 | serviceAccountName
132 | terminationGracePeriodSeconds
133 | tolerations {
134 | effect
135 | key
136 | operator
137 | tolerationSeconds
138 | }
139 | volumes {
140 | name
141 | persistentVolumeClaim { claimName readOnly }
142 | secret { defaultMode secretName }
143 | }
144 | }
145 | }
146 | }
147 | }}`,
148 | `{
149 | "allDeployments": [
150 | {
151 | "metadata": {
152 | "creationTimestamp": "2018-07-02T14:53:53Z",
153 | "generation": 1,
154 | "labels": [
155 | {"name": "app", "value": "clunky-sabertooth-joomla"},
156 | {"name": "chart", "value": "joomla-2.0.2"},
157 | {"name": "heritage", "value": "Tiller"},
158 | {"name": "release", "value": "clunky-sabertooth"}
159 | ]
160 | },
161 | "spec": {
162 | "minReadySeconds": 0,
163 | "paused": false,
164 | "progressDeadlineSeconds": 600,
165 | "replicas": 1,
166 | "revisionHistoryLimit": 10,
167 | "selector": {
168 | "matchExpressions": [],
169 | "matchLabels": [
170 | {"name": "app", "value": "clunky-sabertooth-joomla"}
171 | ]
172 | },
173 | "strategy": {
174 | "rollingUpdate": {
175 | "maxSurgeInt": 1,
176 | "maxSurgeString": null,
177 | "maxUnavailableInt": 1,
178 | "maxUnavailableString": null
179 | },
180 | "type": "RollingUpdate"
181 | },
182 | "template": {
183 | "metadata": {
184 | "creationTimestamp": null,
185 | "labels": [
186 | {"name": "app", "value": "clunky-sabertooth-joomla"}
187 | ]
188 | },
189 | "spec": {
190 | "dnsPolicy": "ClusterFirst",
191 | "restartPolicy": "Always",
192 | "schedulerName": "default-scheduler",
193 | "terminationGracePeriodSeconds": 30,
194 | "volumes": [
195 | {
196 | "name": "joomla-data",
197 | "persistentVolumeClaim": {
198 | "claimName": "clunky-sabertooth-joomla-joomla",
199 | "readOnly": false
200 | }
201 | },
202 | {
203 | "name": "apache-data",
204 | "persistentVolumeClaim": {
205 | "claimName": "clunky-sabertooth-joomla-apache",
206 | "readOnly": false
207 | }
208 | }
209 | ]
210 | }
211 | }
212 | },
213 | "replicaSets": [
214 | {
215 | "metadata": {
216 | "name": "clunky-sabertooth-joomla-5d4ddc985d"
217 | },
218 | "pods": [
219 | {
220 | "metadata": {
221 | "name": "clunky-sabertooth-joomla-5d4ddc985d-fpddz",
222 | "namespace": "default",
223 | "labels": [
224 | {"name": "app", "value": "clunky-sabertooth-joomla"},
225 | {"name": "pod-template-hash", "value": "1808875418"}
226 | ]
227 | },
228 | "spec": {
229 | "dnsPolicy": "ClusterFirst",
230 | "nodeName": "minikube",
231 | "restartPolicy": "Always",
232 | "schedulerName": "default-scheduler",
233 | "serviceAccountName": "default",
234 | "terminationGracePeriodSeconds": 30,
235 | "tolerations": [
236 | {
237 | "effect": "NoExecute",
238 | "key": "node.kubernetes.io/not-ready",
239 | "operator": "Exists",
240 | "tolerationSeconds": 300
241 | },
242 | {
243 | "effect": "NoExecute",
244 | "key": "node.kubernetes.io/unreachable",
245 | "operator": "Exists",
246 | "tolerationSeconds": 300
247 | }
248 | ],
249 | "volumes": [
250 | {
251 | "name": "joomla-data",
252 | "persistentVolumeClaim": {
253 | "claimName": "clunky-sabertooth-joomla-joomla",
254 | "readOnly": false
255 | },
256 | "secret": null
257 | },
258 | {
259 | "name": "apache-data",
260 | "persistentVolumeClaim": {
261 | "claimName": "clunky-sabertooth-joomla-apache",
262 | "readOnly": false
263 | },
264 | "secret": null
265 | },
266 | {
267 | "name": "default-token-l6lb2",
268 | "persistentVolumeClaim": null,
269 | "secret": {
270 | "defaultMode": 420,
271 | "secretName": "default-token-l6lb2"
272 | }
273 | }
274 | ]
275 | }
276 | }
277 | ]
278 | }
279 | ]
280 | }
281 | ]
282 | }`)
283 | simpletest(
284 | t,
285 | `{
286 | podByName(namespace: "default",
287 | name: "clunky-sabertooth-joomla-5d4ddc985d-fpddz") {
288 | owner { metadata { name } }
289 | rootOwner { metadata { name } }
290 | }
291 | }`,
292 | `{
293 | "podByName": {
294 | "owner": {
295 | "metadata": { "name": "clunky-sabertooth-joomla-5d4ddc985d" }
296 | },
297 | "rootOwner": {
298 | "metadata": { "name": "clunky-sabertooth-joomla" }
299 | }
300 | }
301 | }`)
302 | simpletest(
303 | t,
304 | `{
305 | allPodsInNamespace(namespace: "default") {
306 | owner { metadata { name } }
307 | rootOwner { metadata { name } }
308 | }
309 | }`,
310 | `{
311 | "allPodsInNamespace": [
312 | {
313 | "owner": {
314 | "metadata": { "name": "clunky-sabertooth-joomla-5d4ddc985d" }
315 | },
316 | "rootOwner": {
317 | "metadata": { "name": "clunky-sabertooth-joomla" }
318 | }
319 | }
320 | ]
321 | }`)
322 | simpletest(
323 | t,
324 | `{
325 | allReplicaSets() {
326 | owner { metadata { name } }
327 | rootOwner { metadata { name } }
328 | }
329 | }`,
330 | `{
331 | "allReplicaSets": [
332 | {
333 | "owner": {
334 | "metadata": { "name": "clunky-sabertooth-joomla" }
335 | },
336 | "rootOwner": {
337 | "metadata": { "name": "clunky-sabertooth-joomla" }
338 | }
339 | }
340 | ]
341 | }`)
342 | simpletest(
343 | t,
344 | `{
345 | allDaemonSets() {
346 | owner { metadata { name namespace } }
347 | rootOwner { metadata { name namespace } }
348 | pods { metadata { name labels { name value } } }
349 | }
350 | }`,
351 | `{
352 | "allDaemonSets": [
353 | {
354 | "owner": {
355 | "metadata": {
356 | "name": "calico-node",
357 | "namespace": "kube-system"
358 | }
359 | },
360 | "rootOwner": {
361 | "metadata": {
362 | "name": "calico-node",
363 | "namespace": "kube-system"
364 | }
365 | },
366 | "pods": [
367 | {
368 | "metadata": {
369 | "name": "calico-node-ddxfj",
370 | "labels": [
371 | {"name": "controller-revision-hash",
372 | "value": "3909226423"},
373 | {"name": "k8s-app", "value": "calico-node"},
374 | {"name": "pod-template-generation", "value": "1"}
375 | ]
376 | }
377 | }
378 | ]
379 | }
380 | ]
381 | }`)
382 | simpletest(
383 | t,
384 | `{
385 | allServices() {
386 | owner { metadata { name namespace } }
387 | rootOwner { metadata { name namespace } }
388 | selected { metadata { name labels { name value } } }
389 | }
390 | }`,
391 | `{
392 | "allServices": [
393 | {
394 | "owner": {
395 | "metadata": {
396 | "name": "mongo",
397 | "namespace": "flonjella"
398 | }
399 | },
400 | "rootOwner": {
401 | "metadata": {
402 | "name": "mongo",
403 | "namespace": "flonjella"
404 | }
405 | },
406 | "selected": [
407 | {
408 | "metadata": {
409 | "name": "mongo-0",
410 | "labels": [
411 | {"name": "app", "value": "mongo"},
412 | {"name": "controller-revision-hash",
413 | "value": "mongo-fdd786d"},
414 | {"name": "name", "value": "mongo"},
415 | {"name": "statefulset.kubernetes.io/pod-name",
416 | "value": "mongo-0"}
417 | ]
418 | }
419 | }
420 | ]
421 | }
422 | ]
423 | }`)
424 | simpletest(
425 | t,
426 | `{
427 | allServicesInNamespace(namespace: "flonjella") {
428 | owner { metadata { name namespace } }
429 | rootOwner { metadata { name namespace } }
430 | selected { metadata { name labels { name value } } }
431 | }
432 | }`,
433 | `{
434 | "allServicesInNamespace": [
435 | {
436 | "owner": {
437 | "metadata": {
438 | "name": "mongo",
439 | "namespace": "flonjella"
440 | }
441 | },
442 | "rootOwner": {
443 | "metadata": {
444 | "name": "mongo",
445 | "namespace": "flonjella"
446 | }
447 | },
448 | "selected": [
449 | {
450 | "metadata": {
451 | "name": "mongo-0",
452 | "labels": [
453 | {"name": "app", "value": "mongo"},
454 | {"name": "controller-revision-hash",
455 | "value": "mongo-fdd786d"},
456 | {"name": "name", "value": "mongo"},
457 | {"name": "statefulset.kubernetes.io/pod-name",
458 | "value": "mongo-0"}
459 | ]
460 | }
461 | }
462 | ]
463 | }
464 | ]
465 | }`)
466 | simpletest(
467 | t,
468 | `{
469 | serviceByName(namespace: "flonjella", name: "mongo") {
470 | owner { metadata { name namespace } }
471 | rootOwner { metadata { name namespace } }
472 | selected { metadata { name labels { name value } } }
473 | }
474 | }`,
475 | `{
476 | "serviceByName": {
477 | "owner": {
478 | "metadata": {
479 | "name": "mongo",
480 | "namespace": "flonjella"
481 | }
482 | },
483 | "rootOwner": {
484 | "metadata": {
485 | "name": "mongo",
486 | "namespace": "flonjella"
487 | }
488 | },
489 | "selected": [
490 | {
491 | "metadata": {
492 | "name": "mongo-0",
493 | "labels": [
494 | {"name": "app", "value": "mongo"},
495 | {"name": "controller-revision-hash",
496 | "value": "mongo-fdd786d"},
497 | {"name": "name", "value": "mongo"},
498 | {"name": "statefulset.kubernetes.io/pod-name",
499 | "value": "mongo-0"}
500 | ]
501 | }
502 | }
503 | ]
504 | }
505 | }`)
506 | }
507 |
--------------------------------------------------------------------------------
/gobuild.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | go get -u github.com/kardianos/govendor
4 | $GOPATH/bin/govendor sync
5 | #CGO_ENABLED=0 go build -a -v -ldflags '-s'
6 | go build -gcflags '-N -l'
7 | # # Run unit tests and generate code coverage reports -- an html one
8 | # # for local viewing and a cobertura one for jenkins builds.
9 | #go get -u github.com/t-yuki/gocover-cobertura
10 | # go test -coverprofile coverage.txt
11 | # result=$?
12 | # go tool cover -html=coverage.txt -o coverage.html
13 | # $GOPATH/bin/gocover-cobertura < coverage.txt > coverage.xml
14 | exit $result
15 |
16 |
--------------------------------------------------------------------------------
/k8sdeploy.yaml:
--------------------------------------------------------------------------------
1 | # Generated 2018-10-31T22:17:29.025Z by Yipee.io
2 | # Application: kubeiql
3 | # Last Modified: 2018-10-31T22:17:29.025Z
4 |
5 | #
6 | # Kubernetes object definitions for deploying kubeiql in a k8s cluster
7 | #
8 |
9 | # ServiceAccount that will be given cluster-level access permissions
10 | apiVersion: v1
11 | kind: ServiceAccount
12 | metadata:
13 | name: kubeiql-service-account
14 |
15 | ---
16 | # A role allowing kubeiql to watch all system objects
17 | apiVersion: rbac.authorization.k8s.io/v1
18 | kind: ClusterRole
19 | metadata:
20 | name: kubeiql-actor
21 | labels:
22 | aggregate-to-admin: 'true'
23 | aggregate-to-edit: 'true'
24 | rules:
25 | - apiGroups:
26 | - '*'
27 | resources:
28 | - '*'
29 | verbs:
30 | - watch
31 |
32 | ---
33 | # Bind the kubeiql access role to the kubeiql service account
34 | apiVersion: rbac.authorization.k8s.io/v1beta1
35 | kind: ClusterRoleBinding
36 | metadata:
37 | name: kubeiql-actions
38 | roleRef:
39 | apiGroup: rbac.authorization.k8s.io
40 | kind: ClusterRole
41 | name: kubeiql-actor
42 | subjects:
43 | - kind: ServiceAccount
44 | name: kubeiql-service-account
45 | namespace: default
46 |
47 | ---
48 | # Endpoint for accessing kubeiql.
49 | # This configuration works for deploying in local minikube.
50 | # Change type to LoadBalancer (or set up an ingress) for deployment
51 | # in a production environment
52 | apiVersion: v1
53 | kind: Service
54 | metadata:
55 | name: kubeiql
56 | spec:
57 | selector:
58 | yipee.io/kubeiql: generated
59 | ports:
60 | - port: 8128
61 | targetPort: 8128
62 | name: kubeiql-8128
63 | protocol: TCP
64 | nodePort: 32128
65 | type: NodePort
66 |
67 | ---
68 | # Here's the actual kubeiql app. If you want to run a locally built
69 | # image (instead of the one we've pushed to dockerhub), just change
70 | # the image name here accordingly...
71 | apiVersion: extensions/v1beta1
72 | kind: Deployment
73 | metadata:
74 | name: kubeiql
75 | annotations:
76 | yipee.io.lastModelUpdate: '2018-10-31T22:17:28.959Z'
77 | yipee.io.modelURL: http://192.168.99.100:32080/editor
78 | spec:
79 | selector:
80 | matchLabels:
81 | name: kubeiql
82 | component: kubeiql
83 | yipee.io/kubeiql: generated
84 | rollbackTo:
85 | revision: 0
86 | template:
87 | spec:
88 | imagePullSecrets: []
89 | containers:
90 | - name: kubeiql
91 | env:
92 | - name: API_HOST
93 | value: https://kubernetes.default.svc
94 | - name: API_SECRET_PATH
95 | value: /var/run/secrets/kubernetes.io/serviceaccount
96 | ports:
97 | - containerPort: 8128
98 | protocol: TCP
99 | imagePullPolicy: Always
100 | image: yipeeio/kubeiql
101 | restartPolicy: Always
102 | serviceAccountName: kubeiql-service-account
103 | metadata:
104 | labels:
105 | name: kubeiql
106 | component: kubeiql
107 | yipee.io/kubeiql: generated
108 | strategy:
109 | type: RollingUpdate
110 | rollingUpdate: {}
111 | replicas: 1
112 |
--------------------------------------------------------------------------------
/kubeaccess.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | "encoding/json"
20 | "fmt"
21 | )
22 |
23 | // Functions for retrieving Kubernetes information from a cluster
24 |
25 | // Get a single resource instance from a namespace
26 | func getK8sResource(ctx context.Context, kind, namespace, name string) resource {
27 | return lookUpResource(ctx, kind, namespace, name)
28 | }
29 |
30 | func getRawK8sResource(
31 | ctx context.Context, kind, namespace, name string) JsonObject {
32 | return lookUpMap(ctx, kind, namespace, name)
33 | }
34 |
35 | func fromJson(val []byte) interface{} {
36 | var result interface{}
37 |
38 | if err := json.Unmarshal(val, &result); err != nil {
39 | panic(err)
40 | }
41 |
42 | return result
43 | }
44 |
45 | var testContext *context.Context = nil
46 |
47 | func setTestContext(ctx *context.Context) {
48 | testContext = ctx
49 | }
50 |
51 | func getTestContext() *context.Context {
52 | return testContext
53 | }
54 |
55 | func getCache(inctx context.Context) *JsonObject {
56 | ctx := &inctx
57 | if isTest() {
58 | ctx = getTestContext()
59 | }
60 | return (*ctx).Value("queryCache").(*JsonObject)
61 | }
62 |
63 | func isTest() bool {
64 | return testContext != nil
65 | }
66 |
67 | func lookUpMap(
68 | ctx context.Context,
69 | kind, namespace, name string) JsonObject {
70 | key := cacheKey(kind, namespace, name)
71 | var cachedVal interface{}
72 | if !isWatchedKind(kind) {
73 | panic(fmt.Sprintf("Add watcher for kind '%s'", kind))
74 | }
75 | cachedVal = GetCache().Lookup(key)
76 | if cachedVal == nil {
77 | return nil
78 | }
79 | return cachedVal.(JsonObject)
80 | }
81 |
82 | func lookUpResource(ctx context.Context, kind, namespace, name string) resource {
83 | mapval := lookUpMap(ctx, kind, namespace, name)
84 |
85 | if mapval == nil {
86 | return nil
87 | }
88 |
89 | return mapToResource(ctx, mapval)
90 | }
91 |
92 | func getCachedResourceList(
93 | ctx context.Context,
94 | cacheKey string,
95 | test func(JsonObject) bool) []resource {
96 |
97 | var cachedJsonObjs []JsonObject
98 | var results []resource
99 |
100 | if objs := GetCache().Lookup(cacheKey); objs != nil {
101 | cachedJsonObjs = objs.([]JsonObject)
102 | }
103 | for _, res := range cachedJsonObjs {
104 | val := mapToResource(ctx, res)
105 | if test(res) {
106 | results = append(results, val)
107 | }
108 | }
109 | if results == nil {
110 | results = make([]resource, 0)
111 | }
112 | return results
113 | }
114 |
115 | // Get all resource instances of a specific kind
116 | func getAllK8sObjsOfKind(
117 | ctx context.Context,
118 | kind string,
119 | test func(JsonObject) bool) []resource {
120 |
121 | if !isWatchedKind(kind) {
122 | panic(fmt.Sprintf("Add watcher for kind '%s'", kind))
123 | }
124 | return getCachedResourceList(ctx, kind, test)
125 | }
126 |
127 | // Get all resource instances of a specific kind in a specific namespace
128 | func getAllK8sObjsOfKindInNamespace(
129 | ctx context.Context,
130 | kind, ns string,
131 | test func(JsonObject) bool) []resource {
132 | key := nsCacheKey(kind, ns)
133 | if !isWatchedKind(kind) {
134 | panic(fmt.Sprintf("Add watcher for kind '%s'", kind))
135 | }
136 | return getCachedResourceList(ctx, key, test)
137 | }
138 |
--------------------------------------------------------------------------------
/kubeiql.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | )
20 |
21 | // The schema below defines the objects and relationships for Kubernetes.
22 | // It is not yet complete.
23 |
24 | var Schema = `
25 | schema {
26 | query: Query
27 | mutation: Mutation
28 | }
29 | # The query type, represents all of the entry points into our object graph
30 | type Query {
31 | # look up pods
32 | allPods(): [Pod]
33 | allPodsInNamespace(namespace: String!): [Pod]
34 | podByName(namespace: String!, name: String!): Pod
35 | # look up deployments
36 | allDeployments(): [Deployment]
37 | allDeploymentsInNamespace(namespace: String!): [Deployment]
38 | deploymentByName(namespace: String!, name: String!): Deployment
39 | # look up replica sets
40 | allReplicaSets(): [ReplicaSet]
41 | allReplicaSetsInNamespace(namespace: String!): [ReplicaSet]
42 | replicaSetByName(namespace: String!, name: String!): ReplicaSet
43 | # look up daemon sets
44 | allDaemonSets(): [DaemonSet]
45 | allDaemonSetsInNamespace(namespace: String!): [DaemonSet]
46 | daemonSetByName(namespace: String!, name: String!): DaemonSet
47 | # look up stateful sets
48 | allStatefulSets(): [StatefulSet]
49 | allStatefulSetsInNamespace(namespace: String!): [StatefulSet]
50 | statefulSetByName(namespace: String!, name: String!): StatefulSet
51 | # look up services
52 | allServices(): [Service]
53 | allServicesInNamespace(namespace: String!): [Service]
54 | serviceByName(namespace: String!, name: String!): Service
55 | }
56 |
57 | # The mutation type, represents all updates we can make to our data
58 | type Mutation {
59 | }
60 |
61 | # Available logging levels
62 | enum LogLevel {
63 | debug
64 | info
65 | warning
66 | error
67 | fatal
68 | panic
69 | }
70 |
71 | # A service
72 | type Service implements Resource {
73 | # The metadata for the service (name, labels, namespace, etc.)
74 | metadata: Metadata!
75 | # The description for the service
76 | spec: ServiceSpec!
77 | # The direct owner of the replicaSet
78 | owner: Resource
79 | # The root owner of the replicaSet
80 | rootOwner: Resource
81 | # Which controllers does this service select?
82 | selected: [Resource!]!
83 | # Runtime status
84 | # status ServiceStatus! XXX
85 | }
86 |
87 | # Description of a service
88 | type ServiceSpec {
89 | # IP Address of the service
90 | clusterIP: String
91 | # External IPs
92 | externalIPs: [String!]
93 | # External reference for kubedns
94 | externalName: String
95 | # How a service should redirect to ports
96 | externalTrafficPolicy: String
97 | # Port used by health checks
98 | healthCheckNodePort: Int
99 | # IP for load balancer
100 | loadBalancerIP: String
101 | # Ranges from which to choose load balancer IPs
102 | loadBalancerSourceRanges: [String!]
103 | # Exposed ports
104 | ports: [ServicePort!]
105 | # Whether to publish dns addresses when peers are not yet ready for
106 | # discovery
107 | publishNotReadyAddresses: Boolean
108 | # Labels to select controllers for association with the service
109 | selector: [Label!]!
110 | # Whether or not to use session affinity ("ClientIP" or "None")
111 | sessionAffinity: String
112 | # Config for session affinity
113 | sessionAffinityConfig: SessionAffinityConfig
114 | # Type of service
115 | type: ServiceType!
116 | }
117 |
118 | # A port managed by a service
119 | type ServicePort {
120 | # port name, if any -- required if multiple ports
121 | name: String
122 | # port allocated to each node on which this service is exposed
123 | nodePort: Int
124 | # port exposed by the service
125 | port: Int!
126 | # IP Protocol - "TCP" or "UDP", default: "TCP"
127 | protocol: String
128 | # Number or name of port on the pods targeted by the service
129 | targetPortString: String
130 | targetPortInt: Int
131 | }
132 |
133 | # Service Types
134 | enum ServiceType { ClientIP NodePort ExternalName LoadBalancer }
135 |
136 | # Session affinity config
137 | type SessionAffinityConfig {
138 | # config for client IP
139 | clientIP: ClientIPConfig!
140 | }
141 |
142 | # Client IP config
143 | type ClientIPConfig {
144 | # timeout between 0 and 86400 (one day)
145 | timeoutSeconds: Int!
146 | }
147 |
148 | # A pod
149 | type Pod implements Resource {
150 | # The metadata for the pod (name, labels, namespace, etc.)
151 | metadata: Metadata!
152 | # behavior specification
153 | spec: PodSpec!
154 | # The direct owner of the pod
155 | owner: Resource
156 | # The root owner of the pod
157 | rootOwner: Resource
158 | # XXX PodStatus
159 | }
160 |
161 | # A replicaSet
162 | type ReplicaSet implements Resource {
163 | # The metadata for the replicaSet (name, labels, namespace, etc.)
164 | metadata: Metadata!
165 | # The direct owner of the replicaSet
166 | owner: Resource
167 | # The root owner of the replicaSet
168 | rootOwner: Resource
169 | # The pods controlled by this replicaSet
170 | pods: [Pod!]!
171 | }
172 |
173 | # A statefulSet
174 | type StatefulSet implements Resource {
175 | # The metadata for the statefulSet (name, labels, namespace, etc.)
176 | metadata: Metadata!
177 | # The direct owner of the statefulSet
178 | owner: Resource
179 | # The root owner of the statefulSet
180 | rootOwner: Resource
181 | # The pods controlled by this statefulSet
182 | pods: [Pod!]!
183 | }
184 |
185 | # A daemonSet
186 | type DaemonSet implements Resource {
187 | # The metadata for the daemonSet (name, labels, namespace, etc.)
188 | metadata: Metadata!
189 | # The direct owner of the daemonSet
190 | owner: Resource
191 | # The root owner of the daemonSet
192 | rootOwner: Resource
193 | # The pods controlled by this daemonSet
194 | pods: [Pod!]!
195 | }
196 |
197 | # A deployment
198 | type Deployment implements Resource {
199 | # The metadata for the deployment (name, labels, namespace, etc.)
200 | metadata: Metadata!
201 | # Description of the deployment
202 | spec: DeploymentSpec!
203 | # The direct owner of the deployment
204 | owner: Resource
205 | # The root owner of the deployment
206 | rootOwner: Resource
207 | # The replicaSets that are children of this deployment
208 | replicaSets: [ReplicaSet!]!
209 | }
210 |
211 | # A deployment specification
212 | type DeploymentSpec {
213 | # Minimum number of seconds for which a newly created pod should be
214 | # ready without any of its container crashing, for it to be considered
215 | # available (defaults to 0)
216 | minReadySeconds: Int!
217 | # Whether or not the deployment is paused
218 | paused: Boolean!
219 | # The maximum time in seconds for a deployment to make progress before
220 | # it is considered to be failed.
221 | progressDeadlineSeconds: Int!
222 | # Number of desired pods (default: 1).
223 | replicas: Int!
224 | # The number of old ReplicaSets to retain to allow rollback (default: 10).
225 | revisionHistoryLimit: Int!
226 | # Label selector for pods.
227 | selector: LabelSelector
228 | # The deployment strategy to use to replace existing pods with new ones.
229 | strategy: DeploymentStrategy!
230 | # Template describing the pods that will be created.
231 | template: PodTemplateSpec!
232 | }
233 |
234 | # metadata
235 | type Metadata { # annotations??
236 | # When was the decorated object created
237 | creationTimestamp: String
238 | # Prefix for generated names
239 | generateName: String
240 | # Sequence number for state transitions
241 | generation: Int
242 | # Top level labels
243 | labels: [Label!]!
244 | # Generated name
245 | name: String
246 | # Namespace containing the object
247 | namespace: String
248 | # All owners
249 | ownerReferences: [Resource!]
250 | # Version
251 | resourceVersion: String
252 | # How to find this object
253 | selfLink: String
254 | # UUID
255 | uid: String
256 | }
257 |
258 | # PodTemplateSpec
259 | type PodTemplateSpec {
260 | # standard metadata
261 | metadata: Metadata!
262 | # Specification for generated pods
263 | spec: PodSpec!
264 | }
265 |
266 | # PodSpec
267 | type PodSpec {
268 | # Optional duration in seconds the pod may be active on the node
269 | # relative to StartTime before the system will actively try to mark it
270 | # failed and kill associated containers. Value must be a positive integer.
271 | activeDeadlineSeconds: Int
272 | # Scheduling constraints for the pod, if specified
273 | affinity: Affinity
274 | # AutomountServiceAccountToken indicates whether a service account token
275 | # should be automatically mounted.
276 | automountServiceAccountToken: Boolean!
277 | # List of containers belonging to the pod
278 | containers: [Container!]!
279 | # DNS parameters of a pod
280 | dnsConfig: PodDNSConfig
281 | # DNS policy for the pod -- defaults to "ClusterFirst"
282 | dnsPolicy: DNSPolicy
283 | # List of hosts and IPs to inject into pod host file
284 | hostAliases: [HostAlias!]
285 | # Whether or not to use the host ipc namespace -- default: false
286 | hostIPC: Boolean!
287 | # Whether or not to use host networking -- default: false
288 | hostNetwork: Boolean!
289 | # Whether or not to use the host pid namespace -- default: false
290 | hostPID: Boolean!
291 | # Hostname for the pod -- defaults to system-generated value
292 | hostname: String
293 | # List of references to secrets in the same namespace used to pull images
294 | imagePullSecrets: [LocalObjectReference!]
295 | # Initialization containers for the pod
296 | initContainers: [Container!]
297 | # Name of specific host on which to schedule the pod (if any)
298 | nodeName: String
299 | # Priority of the pod
300 | priority: Int
301 | # Class name of priority class for the pod
302 | priorityClassName: String
303 | # Conditions to evaluate for pod readiness
304 | readinessGates: [PodReadinessGate!]
305 | # Restart policy for all containers within the pod -- default: Always
306 | restartPolicy: RestartPolicy!
307 | # Specific scheduler to use for pod
308 | schedulerName: String
309 | # Pod-level security attributes and common container settings
310 | securityContext: PodSecurityContext
311 | # Name of service account to use when running this pod
312 | serviceAccountName: String
313 | # Whether or not to share a single process namespace between all containers
314 | # default: false
315 | shareProcessNamespace: Boolean!
316 | # Desired pod subdomain, if any
317 | subdomain: String
318 | # Duration in seconds the pod needs to terminate gracefully -- default: 30s
319 | terminationGracePeriodSeconds: Int!
320 | # Tolerations for the pod
321 | tolerations: [Toleration!]
322 | # List of volumes that can be mounted by containers in the pod
323 | volumes: [Volume!]
324 | }
325 |
326 | # Persistent storage
327 | type Volume {
328 | #
329 | # INCOMPLETE
330 | #
331 |
332 | # config map used to populate the volume
333 | configMap: ConfigMapVolumeSource
334 | # volume name -- must be a DNS_LABEL and unique within the pod
335 | name: String!
336 | # a pre-existing file or directory on the host machine
337 | hostPath: HostPathVolumeSource
338 | # reference to a persistent volume claim
339 | persistentVolumeClaim: PersistentVolumeClaimVolumeSource
340 | # a secret that should populate the volume
341 | secret: SecretVolumeSource
342 | }
343 |
344 | # Volume sources
345 | type ConfigMapVolumeSource {
346 | # mode bits to use on created files by default -- default value: 0644
347 | defaultMode: Int
348 | # specific config map items to expose -- if unset, expose all
349 | items: [KeyToPath!]
350 | # name of map
351 | name: String!
352 | # Is it okay if the config map does not exist? -- default: false
353 | optional: Boolean!
354 | }
355 |
356 | type HostPathVolumeSource {
357 | # path of the directory on the host
358 | path: String!
359 | # type for host path volume -- defaults to: ""
360 | type: String
361 | }
362 |
363 | type PersistentVolumeClaimVolumeSource {
364 | # name of a PersistentVolumeClaim in the same namespace as the pod
365 | claimName: String!
366 | # Is the claim read only? -- default: false
367 | readOnly: Boolean!
368 | }
369 |
370 | type SecretVolumeSource {
371 | # mode bits to use on created files by default -- default value: 0644
372 | defaultMode: Int
373 | # specific secret items to expose -- if unset, expose all
374 | items: [KeyToPath!]
375 | # Is it okay if the secret does not exist? -- default: false
376 | optional: Boolean!
377 | # name of secret
378 | secretName: String!
379 | }
380 |
381 | # kernel parameters to set
382 | type Sysctl {
383 | # name of a parameter
384 | name: String!
385 | # parameter value
386 | value: String!
387 | }
388 |
389 | # mapping of string key to path within a volume
390 | type KeyToPath {
391 | # the key to project
392 | key: String!
393 | # mode bits to use on the file [0 .. 0777]; if empty, uses volume default
394 | mode: Int
395 | # relative path of the file to map
396 | path: String!
397 | }
398 |
399 | # Reference to a pod condition
400 | type PodReadinessGate {
401 | # a condition in the condition list for the pod
402 | conditionType: String!
403 | }
404 |
405 | # Pod-level security attributes and common container settings
406 | type PodSecurityContext {
407 | # supplemental group that applies to all containers in a pod
408 | fsGroup: Int
409 | # GID for container process entrypoint
410 | runAsGroup: Int
411 | # Must the container run as non-root?
412 | runAsNonRoot: Boolean!
413 | # UID for container process entrypoint
414 | runAsUser: Int
415 | # SELinux context to apply to all containers
416 | seLinuxOptions: SELinuxOptions
417 | # list of groups applied to the first process run in each container in
418 | # addition to the primary group
419 | supplementalGroups: [Int!]
420 | # list of namespaced sysctls user for the pod
421 | sysctls: [Sysctl!]
422 | }
423 |
424 | # Conditions determining whether a node can host a particular pod
425 | type Toleration {
426 | # the taint effect to match
427 | effect: TolerationEffect
428 | # the taint key the toleration references; empty means match all
429 | key: String
430 | # relationship between key and value
431 | operator: TolerationOperator
432 | # the period of time the toleration tolerates the taint; unset means
433 | # forever, zero or negative means immediately evict
434 | tolerationSeconds: Int
435 | # taint value matched by the toleration -- should be empty if operator
436 | # is "Exists"
437 | value: String
438 | }
439 |
440 | # Operators for tolerations
441 | enum TolerationOperator {
442 | Exists Equal
443 | }
444 |
445 | # Toleration effect possible values
446 | enum TolerationEffect {
447 | NoSchedule PreferNoSchedule NoExecute
448 | }
449 |
450 | # SELinux labels to apply to a container
451 | type SELinuxOptions {
452 | # level label
453 | level: String
454 | # role label
455 | role: String
456 | # type label
457 | type: String
458 | #user label
459 | user: String
460 | }
461 |
462 | # LocalObjectReference
463 | type LocalObjectReference {
464 | # name of referenced object
465 | name: String!
466 | }
467 |
468 | # DNS support
469 | type PodDNSConfig {
470 | # list of DNS name server IP addresses
471 | nameservers: [String!]
472 | # DNS resolver options
473 | options: [PodDNSConfigOption!]
474 | # DNS search domains for host-name lookup
475 | searches: [String!]
476 | }
477 |
478 | # Options for DNS config
479 | type PodDNSConfigOption {
480 | name: String!
481 | value: String!
482 | }
483 |
484 | # HostAlias
485 | type HostAlias {
486 | # Hostnames for the IP address
487 | hostnames: [String!]!
488 | # IP address of the host
489 | ip: String!
490 | }
491 |
492 | # Container
493 | type Container {
494 | # XXX not yet
495 | }
496 |
497 | # Affinity
498 | type Affinity {
499 | # affinity with nodes
500 | nodeAffinity: NodeAffinity
501 | # affinity with other pods
502 | podAffinity: PodAffinity
503 | # anti-affinity with other pods
504 | podAntiAffinity: PodAntiAffinity
505 | }
506 |
507 | # NodeAffinity
508 | type NodeAffinity {
509 | # nodes satisfying these conditions will be preferred
510 | preferredDuringSchedulingIgnoredDuringExecution: [PreferredSchedulingTerm!]
511 | # nodes satisfying these conditions will be required
512 | requiredDuringSchedulingIgnoredDuringExecution: NodeSelector
513 | }
514 |
515 | # NodeSelector
516 | type NodeSelector {
517 | # list of terms used to match nodes for deployment (ORed together)
518 | nodeSelectorTerms: [NodeSelectorTerm!]!
519 | }
520 |
521 | # NodeSelectorTerm
522 | type NodeSelectorTerm {
523 | # node requirements based on node labels
524 | matchExpressions: [NodeSelectorRequirement!]
525 | # node requirements based on node fields
526 | matchFields: [NodeSelectorRequirement!]
527 | }
528 |
529 | # Requirement expression matched against node labels
530 | type NodeSelectorRequirement {
531 | # The node key that the selector applies to
532 | key: String!
533 | # The expression operator
534 | operator: NodeOperator!
535 | # The values to match against
536 | values: [String!]
537 | }
538 |
539 | # PodAffinity
540 | type PodAffinity {
541 | # nodes satisfying these conditions will be preferred
542 | preferredDuringSchedulingIgnoredDuringExecution: [WeightedPodAffinityTerm!]
543 | # nodes satisfying these conditions will be required
544 | requiredDuringSchedulingIgnoredDuringExecution: PodAffinityTerm
545 | }
546 |
547 | # PodAntiAffinity
548 | type PodAntiAffinity {
549 | # nodes satisfying these conditions will be preferred
550 | preferredDuringSchedulingIgnoredDuringExecution: [WeightedPodAffinityTerm!]
551 | # nodes satisfying these conditions will be required
552 | requiredDuringSchedulingIgnoredDuringExecution: PodAffinityTerm
553 | }
554 |
555 | # WeightedPodAffinityTerm
556 | type WeightedPodAffinityTerm {
557 | # term associated with a weight
558 | podAffinityTerm: PodAffinityTerm!
559 | # weight for the term
560 | weight: Int!
561 | }
562 |
563 | # PodAffinityTerm
564 | type PodAffinityTerm {
565 | # selector to match other pod labels
566 | labelSelector: LabelSelector
567 | # which namespaces to match against -- defaults to the pod namespace
568 | namespaces: [String!]
569 | # whether the pod should be co-located or not co-located with pods
570 | # matching the selector
571 | topologyKey: String!
572 | }
573 |
574 | # PreferredSchedulingTerm
575 | type PreferredSchedulingTerm {
576 | # node preference
577 | preference: NodeSelectorTerm!
578 | # weight associated with the term
579 | weight: Int!
580 | }
581 |
582 | # Node operator values
583 | enum NodeOperator {
584 | In NotIn Exists DoesNotExist Gt Lt
585 | }
586 |
587 | # RestartPolicy values
588 | enum RestartPolicy {
589 | Always OnFailure Never
590 | }
591 |
592 | # DNSPolicy values
593 | enum DNSPolicy {
594 | ClusterFirstWithHostNet ClusterFirst Default None
595 | }
596 |
597 | # LabelSelector for matching pods
598 | type LabelSelector {
599 | # constraint expressions for labels
600 | matchExpressions: [LabelSelectorRequirement!]
601 | # key/value matches
602 | matchLabels: [Label!]
603 | }
604 |
605 | # Constraint expression for labels
606 | type LabelSelectorRequirement {
607 | # The label key that the selector applies to
608 | key: String!
609 | # The expression operator
610 | operator: LabelOperator!
611 | # The values to match against
612 | values: [String!]!
613 | }
614 |
615 | # Constraint operators for labels
616 | enum LabelOperator {
617 | In NotIn Exists DoesNotExist
618 | }
619 |
620 | # deployment strategy
621 | type DeploymentStrategy {
622 | # Rolling update config parameters
623 | rollingUpdate: RollingUpdateDeployment
624 | # Type of deployment
625 | type: DeploymentStrategyType
626 | }
627 |
628 | # Types of deployment strategy
629 | enum DeploymentStrategyType {
630 | Recreate RollingUpdate
631 | }
632 |
633 | # The following section is a mess due to the questionable decision by
634 | # the Kubernetes team to make certain fields contain either ints or
635 | # strings (WHY?????)
636 |
637 | # rolling update parameters
638 | type RollingUpdateDeployment {
639 | # The maximum number of pods that can be scheduled above the desired
640 | # number of pods.
641 | maxSurgeInt: Int
642 | maxSurgeString: String
643 | # The maximum number of pods that can be unavailable during the update.
644 | maxUnavailableInt: Int
645 | maxUnavailableString: String
646 | }
647 |
648 | # A label
649 | type Label {
650 | # label name
651 | name: String!
652 | # label value
653 | value: String!
654 | }
655 |
656 | # Any Kubernetes resource
657 | interface Resource {
658 | # type of resource
659 | kind: String!
660 | # resource metadata
661 | metadata: Metadata!
662 | # resource direct owner
663 | owner: Resource
664 | # resource root owner
665 | rootOwner: Resource
666 | }
667 | `
668 |
669 | const PodKind = "Pod"
670 | const ReplicaSetKind = "ReplicaSet"
671 | const StatefulSetKind = "StatefulSet"
672 | const DaemonSetKind = "DaemonSet"
673 | const DeploymentKind = "Deployment"
674 | const ServiceKind = "Service"
675 |
676 | // The root of all queries and mutations. All defined queries and mutations
677 | // start as methods on Resolver
678 | type Resolver struct {
679 | }
680 |
681 | // Objects in json are unmarshalled into map[string]interface{}
682 | type JsonObject = map[string]interface{}
683 | type JsonArray = []interface{}
684 |
685 | // Pod lookups
686 | func (r *Resolver) AllPods(ctx context.Context) *[]*podResolver {
687 | pset := getAllK8sObjsOfKind(
688 | ctx,
689 | PodKind,
690 | func(jobj JsonObject) bool { return true })
691 |
692 | results := make([]*podResolver, len(pset))
693 |
694 | for idx, p := range pset {
695 | results[idx] = p.(*podResolver)
696 | }
697 |
698 | return &results
699 | }
700 |
701 | func (r *Resolver) AllPodsInNamespace(
702 | ctx context.Context,
703 | args *struct {
704 | Namespace string
705 | }) *[]*podResolver {
706 | pset := getAllK8sObjsOfKindInNamespace(
707 | ctx,
708 | PodKind,
709 | args.Namespace,
710 | func(jobj JsonObject) bool { return true })
711 |
712 | results := make([]*podResolver, len(pset))
713 |
714 | for idx, p := range pset {
715 | results[idx] = p.(*podResolver)
716 | }
717 |
718 | return &results
719 | }
720 |
721 | func (r *Resolver) PodByName(
722 | ctx context.Context,
723 | args *struct {
724 | Namespace string
725 | Name string
726 | }) *podResolver {
727 | res := getK8sResource(ctx, PodKind, args.Namespace, args.Name)
728 | if res == nil {
729 | return nil
730 | }
731 | return res.(*podResolver)
732 | }
733 |
734 | // Deployment lookups
735 | func (r *Resolver) AllDeployments(ctx context.Context) *[]*deploymentResolver {
736 | dset := getAllK8sObjsOfKind(
737 | ctx,
738 | DeploymentKind,
739 | func(jobj JsonObject) bool { return true })
740 |
741 | results := make([]*deploymentResolver, len(dset))
742 |
743 | for idx, d := range dset {
744 | results[idx] = d.(*deploymentResolver)
745 | }
746 |
747 | return &results
748 | }
749 |
750 | func (r *Resolver) AllDeploymentsInNamespace(
751 | ctx context.Context,
752 | args *struct {
753 | Namespace string
754 | }) *[]*deploymentResolver {
755 | dset := getAllK8sObjsOfKindInNamespace(
756 | ctx,
757 | DeploymentKind,
758 | args.Namespace,
759 | func(jobj JsonObject) bool { return true })
760 |
761 | results := make([]*deploymentResolver, len(dset))
762 |
763 | for idx, p := range dset {
764 | results[idx] = p.(*deploymentResolver)
765 | }
766 |
767 | return &results
768 | }
769 |
770 | func (r *Resolver) DeploymentByName(
771 | ctx context.Context,
772 | args *struct {
773 | Namespace string
774 | Name string
775 | }) *deploymentResolver {
776 | res := getK8sResource(ctx, DeploymentKind, args.Namespace, args.Name)
777 | if res == nil {
778 | return nil
779 | }
780 | return res.(*deploymentResolver)
781 | }
782 |
783 | // ReplicaSet lookups
784 | func (r *Resolver) AllReplicaSets(ctx context.Context) *[]*replicaSetResolver {
785 | rset := getAllK8sObjsOfKind(
786 | ctx,
787 | ReplicaSetKind,
788 | func(jobj JsonObject) bool { return true })
789 |
790 | results := make([]*replicaSetResolver, len(rset))
791 |
792 | for idx, r := range rset {
793 | results[idx] = r.(*replicaSetResolver)
794 | }
795 |
796 | return &results
797 | }
798 |
799 | func (r *Resolver) AllReplicaSetsInNamespace(
800 | ctx context.Context,
801 | args *struct {
802 | Namespace string
803 | }) *[]*replicaSetResolver {
804 | rset := getAllK8sObjsOfKindInNamespace(
805 | ctx,
806 | ReplicaSetKind,
807 | args.Namespace,
808 | func(jobj JsonObject) bool { return true })
809 |
810 | results := make([]*replicaSetResolver, len(rset))
811 |
812 | for idx, p := range rset {
813 | results[idx] = p.(*replicaSetResolver)
814 | }
815 |
816 | return &results
817 | }
818 |
819 | func (r *Resolver) ReplicaSetByName(
820 | ctx context.Context,
821 | args *struct {
822 | Namespace string
823 | Name string
824 | }) *replicaSetResolver {
825 | res := getK8sResource(ctx, ReplicaSetKind, args.Namespace, args.Name)
826 | if res == nil {
827 | return nil
828 | }
829 | return res.(*replicaSetResolver)
830 | }
831 |
832 | // StatefulSet lookups
833 | func (r *Resolver) AllStatefulSets(ctx context.Context) *[]*statefulSetResolver {
834 | sset := getAllK8sObjsOfKind(
835 | ctx,
836 | StatefulSetKind,
837 | func(jobj JsonObject) bool { return true })
838 |
839 | results := make([]*statefulSetResolver, len(sset))
840 |
841 | for idx, s := range sset {
842 | results[idx] = s.(*statefulSetResolver)
843 | }
844 |
845 | return &results
846 | }
847 |
848 | func (r *Resolver) AllStatefulSetsInNamespace(
849 | ctx context.Context,
850 | args *struct {
851 | Namespace string
852 | }) *[]*statefulSetResolver {
853 | sset := getAllK8sObjsOfKindInNamespace(
854 | ctx,
855 | StatefulSetKind,
856 | args.Namespace,
857 | func(jobj JsonObject) bool { return true })
858 |
859 | results := make([]*statefulSetResolver, len(sset))
860 |
861 | for idx, p := range sset {
862 | results[idx] = p.(*statefulSetResolver)
863 | }
864 |
865 | return &results
866 | }
867 |
868 | func (r *Resolver) StatefulSetByName(
869 | ctx context.Context,
870 | args *struct {
871 | Namespace string
872 | Name string
873 | }) *statefulSetResolver {
874 | res := getK8sResource(ctx, StatefulSetKind, args.Namespace, args.Name)
875 | if res == nil {
876 | return nil
877 | }
878 | return res.(*statefulSetResolver)
879 | }
880 |
881 | // Service lookups
882 | func (r *Resolver) AllServices(ctx context.Context) *[]*serviceResolver {
883 | sset := getAllK8sObjsOfKind(
884 | ctx,
885 | ServiceKind,
886 | func(jobj JsonObject) bool { return true })
887 |
888 | results := make([]*serviceResolver, len(sset))
889 |
890 | for idx, s := range sset {
891 | results[idx] = s.(*serviceResolver)
892 | }
893 |
894 | return &results
895 | }
896 |
897 | func (r *Resolver) AllServicesInNamespace(
898 | ctx context.Context,
899 | args *struct {
900 | Namespace string
901 | }) *[]*serviceResolver {
902 | sset := getAllK8sObjsOfKindInNamespace(
903 | ctx,
904 | ServiceKind,
905 | args.Namespace,
906 | func(jobj JsonObject) bool { return true })
907 |
908 | results := make([]*serviceResolver, len(sset))
909 |
910 | for idx, p := range sset {
911 | results[idx] = p.(*serviceResolver)
912 | }
913 |
914 | return &results
915 | }
916 |
917 | func (r *Resolver) ServiceByName(
918 | ctx context.Context,
919 | args *struct {
920 | Namespace string
921 | Name string
922 | }) *serviceResolver {
923 | res := getK8sResource(ctx, ServiceKind, args.Namespace, args.Name)
924 | if res == nil {
925 | return nil
926 | }
927 | return res.(*serviceResolver)
928 | }
929 |
930 | // DaemonSet lookups
931 | func (r *Resolver) AllDaemonSets(ctx context.Context) *[]*daemonSetResolver {
932 | dset := getAllK8sObjsOfKind(
933 | ctx,
934 | DaemonSetKind,
935 | func(jobj JsonObject) bool { return true })
936 |
937 | results := make([]*daemonSetResolver, len(dset))
938 |
939 | for idx, d := range dset {
940 | results[idx] = d.(*daemonSetResolver)
941 | }
942 |
943 | return &results
944 | }
945 |
946 | func (r *Resolver) AllDaemonSetsInNamespace(
947 | ctx context.Context,
948 | args *struct {
949 | Namespace string
950 | }) *[]*daemonSetResolver {
951 | dset := getAllK8sObjsOfKindInNamespace(
952 | ctx,
953 | DaemonSetKind,
954 | args.Namespace,
955 | func(jobj JsonObject) bool { return true })
956 |
957 | results := make([]*daemonSetResolver, len(dset))
958 |
959 | for idx, p := range dset {
960 | results[idx] = p.(*daemonSetResolver)
961 | }
962 |
963 | return &results
964 | }
965 |
966 | func (r *Resolver) DaemonSetByName(
967 | ctx context.Context,
968 | args *struct {
969 | Namespace string
970 | Name string
971 | }) *daemonSetResolver {
972 | res := getK8sResource(ctx, DaemonSetKind, args.Namespace, args.Name)
973 | if res == nil {
974 | return nil
975 | }
976 | return res.(*daemonSetResolver)
977 | }
978 |
--------------------------------------------------------------------------------
/label.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | )
20 |
21 | // Single label value within a Kubernetes object
22 | type label struct {
23 | Name string
24 | Value string
25 | }
26 |
27 | type labelResolver struct {
28 | ctx context.Context
29 | l *label
30 | }
31 |
32 | // Translate unmarshalled json into a set of labels
33 | func mapToLabels(lMap JsonObject) *[]label {
34 | var labels []label
35 |
36 | for k, v := range lMap {
37 | labels = append(labels, label{k, v.(string)})
38 | }
39 |
40 | return &labels
41 | }
42 |
43 | func (r labelResolver) Name() string {
44 | return r.l.Name
45 | }
46 |
47 | func (r labelResolver) Value() string {
48 | return r.l.Value
49 | }
50 |
--------------------------------------------------------------------------------
/metadata.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | // "fmt"
20 | "sort"
21 | )
22 |
23 | // Kubernetes metadata
24 | type metadata struct {
25 | CreationTimestamp *string
26 | GenerateName *string
27 | Generation *int32
28 | Labels *[]label
29 | Name *string
30 | Namespace *string
31 | OwnerReferences *[]resource
32 | ResourceVersion *string
33 | SelfLink *string
34 | Uid *string
35 | }
36 |
37 | type metadataResolver struct {
38 | ctx context.Context
39 | m metadata
40 | }
41 |
42 | // Translate unmarshalled json into a metadata object
43 | func mapToMetadata(
44 | ctx context.Context, ns string, jsonObj JsonObject) metadata {
45 | var m metadata
46 | var orefs []resource
47 | if ct, ok := jsonObj["creationTimestamp"].(string); ok {
48 | m.CreationTimestamp = &ct
49 | } else {
50 | m.CreationTimestamp = nil
51 | }
52 | if gn, ok := jsonObj["generateName"].(string); ok {
53 | m.GenerateName = &gn
54 | } else {
55 | m.GenerateName = nil
56 | }
57 | if genVal := jsonObj["generation"]; genVal != nil {
58 | if num, ok := genVal.(float64); ok {
59 | numval := int32(num)
60 | m.Generation = &numval
61 | } else {
62 | m.Generation = (genVal.(*int32))
63 | }
64 | }
65 | jg := jgetter(jsonObj)
66 | m.Labels = mapToLabels(mapItem(jsonObj, "labels"))
67 | m.Name = jg.stringRefItemOr("name", nil)
68 | m.Namespace = jg.stringRefItemOr("namespace", nil)
69 | m.ResourceVersion = jg.stringRefItemOr("resourceVersion", nil)
70 | m.SelfLink = jg.stringRefItemOr("selfLink", nil)
71 | m.Uid = jg.stringRefItemOr("uid", nil)
72 |
73 | // Similar to getOwner
74 | if orArray := jsonObj["ownerReferences"]; orArray != nil {
75 | for _, oref := range orArray.(JsonArray) {
76 | ormap := oref.(JsonObject)
77 | orefs = append(
78 | orefs,
79 | getK8sResource(
80 | ctx,
81 | ormap["kind"].(string),
82 | ns,
83 | ormap["name"].(string)))
84 | }
85 | }
86 |
87 | m.OwnerReferences = &orefs
88 | return m
89 | }
90 |
91 | // Metadata methods
92 | func (r metadataResolver) CreationTimestamp() *string {
93 | return r.m.CreationTimestamp
94 | }
95 |
96 | func (r metadataResolver) GenerateName() *string {
97 | return r.m.GenerateName
98 | }
99 |
100 | func (r metadataResolver) Generation() *int32 {
101 | return r.m.Generation
102 | }
103 |
104 | func (r metadataResolver) Labels() []*labelResolver {
105 | var labelResolvers []*labelResolver
106 | for _, label := range *r.m.Labels {
107 | lab := label
108 | labelResolvers = append(labelResolvers, &labelResolver{r.ctx, &lab})
109 | }
110 | sort.Slice(
111 | labelResolvers,
112 | func(i, j int) bool {
113 | return labelResolvers[i].Name() < labelResolvers[j].Name()
114 | })
115 | return labelResolvers
116 | }
117 |
118 | func (r metadataResolver) Name() *string {
119 | return r.m.Name
120 | }
121 |
122 | func (r metadataResolver) Namespace() *string {
123 | return r.m.Namespace
124 | }
125 |
126 | func (r metadataResolver) OwnerReferences() *[]*resourceResolver {
127 | var ownerResolvers []*resourceResolver
128 | for _, owner := range *r.m.OwnerReferences {
129 | own := owner
130 | ownerResolvers = append(ownerResolvers, &resourceResolver{r.ctx, own})
131 | }
132 | return &ownerResolvers
133 | }
134 |
135 | func (r metadataResolver) ResourceVersion() *string {
136 | return r.m.ResourceVersion
137 | }
138 |
139 | func (r metadataResolver) SelfLink() *string {
140 | return r.m.SelfLink
141 | }
142 |
143 | func (r metadataResolver) Uid() *string {
144 | return r.m.Uid
145 | }
146 |
--------------------------------------------------------------------------------
/owner.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | // "fmt"
20 | )
21 |
22 | // Tracks ownership relationships between Kubernetes objects. If an object
23 | // has no owner, we treat it as its own owner
24 | type ownerRef struct {
25 | ctx context.Context
26 | ref JsonObject
27 | cachedOwner resource // cached info for on-demand lookup
28 | }
29 |
30 | // resource method implementations
31 | func (r *ownerRef) Kind() string {
32 | if r.cachedOwner == nil {
33 | r.cachedOwner = getOwner(r.ctx, r.ref)
34 | }
35 | return r.cachedOwner.Kind()
36 | }
37 |
38 | func (r *ownerRef) Metadata() metadataResolver {
39 | if r.cachedOwner == nil {
40 | r.cachedOwner = getOwner(r.ctx, r.ref)
41 | }
42 | return r.cachedOwner.Metadata()
43 | }
44 |
45 | func (r *ownerRef) Owner() *resourceResolver {
46 | if r.cachedOwner == nil {
47 | r.cachedOwner = getOwner(r.ctx, r.ref)
48 | }
49 | return r.cachedOwner.Owner()
50 | }
51 |
52 | func (r *ownerRef) RootOwner() *resourceResolver {
53 | if r.cachedOwner == nil {
54 | r.cachedOwner = getOwner(r.ctx, r.ref)
55 | }
56 | return r.cachedOwner.RootOwner()
57 | }
58 |
59 | // Fetch owners by getting ownerReferences and doing lookups based
60 | // on their contents
61 | func getRawOwner(
62 | ctx context.Context, val JsonObject) JsonObject {
63 | if orefs := getMetadataField(val, "ownerReferences"); orefs != nil {
64 | oArray := orefs.(JsonArray)
65 | if len(oArray) > 0 {
66 | owner := oArray[0].(JsonObject)
67 | if res := getRawK8sResource(
68 | ctx,
69 | owner["kind"].(string),
70 | getNamespace(val),
71 | owner["name"].(string)); res != nil {
72 | return res
73 | }
74 | }
75 | }
76 |
77 | return val
78 | }
79 |
80 | func getOwner(ctx context.Context, val JsonObject) resource {
81 | return mapToResource(ctx, getRawOwner(ctx, val))
82 | }
83 |
84 | func getRootOwner(ctx context.Context, val JsonObject) resource {
85 | result := getRawOwner(ctx, val)
86 |
87 | if getUid(result) == getUid(val) {
88 | return mapToResource(ctx, result)
89 | }
90 |
91 | return getRootOwner(ctx, getRawOwner(ctx, result))
92 | }
93 |
94 | func hasMatchingOwner(jsonObj JsonObject, name, kind string) bool {
95 | if orefs := getMetadataField(jsonObj, "ownerReferences"); orefs != nil {
96 | oArray := orefs.(JsonArray)
97 | for _, oref := range oArray {
98 | owner := oref.(JsonObject)
99 | if owner["name"] == name && owner["kind"] == kind {
100 | return true
101 | }
102 | }
103 | }
104 |
105 | return false
106 | }
107 |
--------------------------------------------------------------------------------
/replicaset.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | // "fmt"
20 | "sort"
21 | "strings"
22 | )
23 |
24 | // ReplicaSets manage replicated pods
25 | type replicaSet struct {
26 | Metadata metadata
27 | Owner resource
28 | RootOwner resource
29 | Pods *[]pod
30 | }
31 |
32 | type replicaSetResolver struct {
33 | ctx context.Context
34 | r replicaSet
35 | }
36 |
37 | // Translate unmarshalled json into a deployment object
38 | func mapToReplicaSet(
39 | ctx context.Context,
40 | jsonObj JsonObject) replicaSet {
41 | placeholder := &ownerRef{ctx, jsonObj, nil}
42 | owner := placeholder
43 | rootOwner := placeholder
44 | meta :=
45 | mapToMetadata(ctx, getNamespace(jsonObj), mapItem(jsonObj, "metadata"))
46 | return replicaSet{meta, owner, rootOwner, nil}
47 | }
48 |
49 | // ReplicaSets have pods as children
50 | func getReplicaSetPods(ctx context.Context, r replicaSet) *[]pod {
51 | rsName := *r.Metadata.Name
52 | rsNamePrefix := rsName + "-"
53 | rsNamespace := *r.Metadata.Namespace
54 |
55 | pset := getAllK8sObjsOfKindInNamespace(
56 | ctx,
57 | PodKind,
58 | rsNamespace,
59 | func(jobj JsonObject) bool {
60 | return (strings.HasPrefix(getName(jobj), rsNamePrefix) &&
61 | hasMatchingOwner(jobj, rsName, ReplicaSetKind))
62 | })
63 |
64 | results := make([]pod, len(pset))
65 |
66 | for idx, p := range pset {
67 | pr := p.(*podResolver)
68 | results[idx] = pr.p
69 | }
70 |
71 | sort.Slice(
72 | results,
73 | func(i, j int) bool {
74 | return *results[i].Metadata.Name < *results[j].Metadata.Name
75 | })
76 |
77 | return &results
78 | }
79 |
80 | // Resource method implementations
81 | func (r *replicaSetResolver) Kind() string {
82 | return ReplicaSetKind
83 | }
84 |
85 | func (r *replicaSetResolver) Metadata() metadataResolver {
86 | return metadataResolver{r.ctx, r.r.Metadata}
87 | }
88 |
89 | func (r *replicaSetResolver) Owner() *resourceResolver {
90 | if oref, ok := r.r.Owner.(*ownerRef); ok {
91 | r.r.Owner = getOwner(oref.ctx, oref.ref)
92 | }
93 | return &resourceResolver{r.ctx, r.r.Owner}
94 | }
95 |
96 | func (r *replicaSetResolver) RootOwner() *resourceResolver {
97 | if oref, ok := r.r.Owner.(*ownerRef); ok {
98 | r.r.Owner = getOwner(oref.ctx, oref.ref)
99 | }
100 | return &resourceResolver{r.ctx, r.r.RootOwner}
101 | }
102 |
103 | // Resolve child Pods
104 | func (r *replicaSetResolver) Pods() []*podResolver {
105 | if r.r.Pods == nil {
106 | r.r.Pods = getReplicaSetPods(r.ctx, r.r)
107 | }
108 |
109 | var res []*podResolver
110 | for _, p := range *r.r.Pods {
111 | res = append(res, &podResolver{r.ctx, p})
112 | }
113 | if res == nil {
114 | res = make([]*podResolver, 0)
115 | }
116 | return res
117 | }
118 |
--------------------------------------------------------------------------------
/resource.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | )
21 |
22 | // Represents all "active" components: (Pods, Deployments, DaemonSets,
23 | // StatefulSets, ReplicaSets
24 |
25 | type resource interface {
26 | Kind() string
27 | Metadata() metadataResolver
28 | Owner() *resourceResolver
29 | RootOwner() *resourceResolver
30 | }
31 |
32 | type resourceResolver struct {
33 | ctx context.Context
34 | r resource
35 | }
36 |
37 | // Translate a map containing unmarshalled json into a resource instance.
38 | func mapToResource(
39 | ctx context.Context,
40 | rMap JsonObject) resource {
41 | kind := getKind(rMap)
42 |
43 | switch kind {
44 | case DeploymentKind:
45 | mtd := mapToDeployment(ctx, rMap)
46 | return &deploymentResolver{ctx, mtd}
47 | case ReplicaSetKind:
48 | return &replicaSetResolver{ctx, mapToReplicaSet(ctx, rMap)}
49 | case DaemonSetKind:
50 | return &daemonSetResolver{ctx, mapToDaemonSet(ctx, rMap)}
51 | case StatefulSetKind:
52 | return &statefulSetResolver{ctx, mapToStatefulSet(ctx, rMap)}
53 | case PodKind:
54 | return &podResolver{ctx, mapToPod(ctx, rMap)}
55 | case ServiceKind:
56 | return &serviceResolver{ctx, mapToService(ctx, rMap)}
57 | }
58 |
59 | fmt.Printf("BAD KIND: %v\n", kind)
60 | return nil
61 | }
62 |
63 | // Turn an instance of a resource into one of its implementers
64 | func (r *resourceResolver) ToPod() (*podResolver, bool) {
65 | c, ok := r.r.(*podResolver)
66 | return c, ok
67 | }
68 |
69 | func (r *resourceResolver) ToReplicaSet() (*replicaSetResolver, bool) {
70 | c, ok := r.r.(*replicaSetResolver)
71 | return c, ok
72 | }
73 |
74 | func (r *resourceResolver) ToDaemonSet() (*daemonSetResolver, bool) {
75 | c, ok := r.r.(*daemonSetResolver)
76 | return c, ok
77 | }
78 |
79 | func (r *resourceResolver) ToStatefulSet() (*statefulSetResolver, bool) {
80 | c, ok := r.r.(*statefulSetResolver)
81 | return c, ok
82 | }
83 |
84 | func (r *resourceResolver) ToDeployment() (*deploymentResolver, bool) {
85 | c, ok := r.r.(*deploymentResolver)
86 | return c, ok
87 | }
88 |
89 | func (r *resourceResolver) ToService() (*serviceResolver, bool) {
90 | c, ok := r.r.(*serviceResolver)
91 | return c, ok
92 | }
93 |
94 | // Implementations of the methods common to all resources
95 | func (r *resourceResolver) Kind() string {
96 | return r.r.Kind()
97 | }
98 |
99 | func (r *resourceResolver) Metadata() metadataResolver {
100 | return r.r.Metadata()
101 | }
102 |
103 | func (r *resourceResolver) Owner() *resourceResolver {
104 | return r.r.Owner()
105 | }
106 |
107 | func (r *resourceResolver) RootOwner() *resourceResolver {
108 | return r.r.RootOwner()
109 | }
110 |
--------------------------------------------------------------------------------
/server.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 CA. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "context"
19 | "fmt"
20 | "github.com/gorilla/mux"
21 | graphql "github.com/neelance/graphql-go"
22 | "github.com/neelance/graphql-go/relay"
23 | "log"
24 | "net/http"
25 | "os"
26 | )
27 |
28 | var KubectlPath = getFromEnv("KUBECTL_PATH", "/usr/local/bin/kubectl")
29 | var schema *graphql.Schema
30 |
31 | var ApiHost = getFromEnv("API_HOST", "http://localhost:8080")
32 | var ApiSecretPath = getFromEnv("API_SECRET_PATH", "")
33 |
34 | func getFromEnv(key, defval string) string {
35 | val, ok := os.LookupEnv(key)
36 | if !ok {
37 | return defval
38 | } else {
39 | return val
40 | }
41 | }
42 |
43 | func init() {
44 | var err error
45 | schema, err = graphql.ParseSchema(Schema, &Resolver{})
46 | fmt.Println(schema)
47 | if err != nil {
48 | panic(err)
49 | }
50 | initCache()
51 | initWatchers()
52 | }
53 |
54 | func main() {
55 | r := mux.NewRouter()
56 | handler := &relay.Handler{Schema: schema}
57 | r.HandleFunc(
58 | "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
59 | w.Write(page)
60 | }))
61 | r.Handle("/query",
62 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
63 | cache := make(JsonObject)
64 | handler.ServeHTTP(w,
65 | r.WithContext(
66 | context.WithValue(
67 | r.Context(),
68 | "queryCache",
69 | &cache)))
70 | })).Methods("POST")
71 | r.HandleFunc("/query",
72 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
73 | w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
74 | })).Methods("OPTIONS")
75 | log.Fatal(http.ListenAndServe(":8128", r))
76 | }
77 |
78 | var page = []byte(`
79 |
80 |
81 |