├── .gitignore
├── CONTRIBUTING.MD
├── LICENSE
├── Makefile
├── README.md
├── Sample.properties
├── apps
├── admin
│ ├── containers
│ │ ├── Dockerfile
│ │ ├── kubernetes.go
│ │ ├── kubernetes_test.go
│ │ ├── main.go
│ │ ├── main_test.go
│ │ └── testdata
│ │ │ ├── deployment_create.json
│ │ │ ├── deployment_delete.json
│ │ │ ├── empty.json
│ │ │ ├── node_schedulable.json
│ │ │ ├── node_unschedulable.json
│ │ │ ├── nodes.json
│ │ │ ├── pod.json
│ │ │ ├── pods.json
│ │ │ └── pods_onenode_only.json
│ ├── docker
│ │ └── Makefile
│ └── kubernetes
│ │ └── Makefile
├── api
│ ├── containers
│ │ ├── Dockerfile
│ │ ├── main.go
│ │ └── main_test.go
│ ├── docker
│ │ └── Makefile
│ └── kubernetes
│ │ └── Makefile
├── game
│ ├── containers
│ │ ├── Dockerfile
│ │ ├── default.conf
│ │ └── default
│ │ │ ├── advanced.html
│ │ │ ├── assets
│ │ │ ├── audio
│ │ │ │ ├── countdown.mp3
│ │ │ │ ├── explosion.wav
│ │ │ │ ├── license.txt
│ │ │ │ ├── pop.wav
│ │ │ │ └── startup.mp3
│ │ │ ├── css
│ │ │ │ ├── alert.png
│ │ │ │ ├── background.png
│ │ │ │ ├── background2.png
│ │ │ │ ├── bomb_waiting2.png
│ │ │ │ ├── cursor.png
│ │ │ │ ├── cursor_down.png
│ │ │ │ ├── cursor_inactive.png
│ │ │ │ ├── cursor_next.png
│ │ │ │ ├── cursor_next_down.png
│ │ │ │ ├── cursor_next_inactive.png
│ │ │ │ ├── cursordown.png
│ │ │ │ ├── holder_background.png
│ │ │ │ ├── license.txt
│ │ │ │ ├── main.css
│ │ │ │ ├── next.css
│ │ │ │ ├── pending.png
│ │ │ │ ├── pending_next.png
│ │ │ │ ├── running.png
│ │ │ │ ├── running_next.png
│ │ │ │ ├── service_down.png
│ │ │ │ ├── service_inactive.png
│ │ │ │ ├── service_up.png
│ │ │ │ ├── sign.png
│ │ │ │ ├── terminating.png
│ │ │ │ └── terminating_next.png
│ │ │ ├── img
│ │ │ │ ├── bomb_explode.png
│ │ │ │ ├── bomb_explode_next.png
│ │ │ │ ├── bomb_waiting.png
│ │ │ │ ├── bomb_waiting_next.png
│ │ │ │ └── license.txt
│ │ │ └── js
│ │ │ │ ├── advanced.js
│ │ │ │ ├── lib.js
│ │ │ │ ├── main.js
│ │ │ │ └── next.js
│ │ │ ├── healthz
│ │ │ └── index.html
│ │ │ ├── index.html
│ │ │ └── next.html
│ ├── docker
│ │ └── Makefile
│ └── kubernetes
│ │ └── Makefile
└── ingress
│ ├── Makefile
│ ├── ingress.generic.yaml
│ ├── ingress.minikube.yaml
│ └── ingress.sample.yaml
├── infrastructure
└── Makefile
└── screenshots
├── advanced.png
├── game.png
└── next.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | Makefile.properties
3 | Makefile.jp.properties
4 | Makefile.us.properties
5 | apps/game/kubernetes/game-deployment.yaml
6 | apps/game/containers/default/assets/js/config.js
7 | apps/admin/kubernetes/admin-deployment.yaml
8 | apps/api/kubernetes/api-deployment.yaml
9 |
10 | /Makefile.au.properties
11 | /Makefile.us.old.properties
12 | /Makefile.eu.properties
13 | /Makefile.sg.properties
14 | /apps/ingress/ingress.yaml
15 | /apps/admin/containers/main
16 | /apps/api/containers/main
17 | /Makefile.de.properties
18 | Makefile.uk.properties
19 |
--------------------------------------------------------------------------------
/CONTRIBUTING.MD:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2017 Google Inc. 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.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | BASEDIR = $(shell pwd)
15 |
16 | include Makefile.properties
17 |
18 | deploy: env creds
19 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) deploy
20 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) deploy
21 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) deploy
22 | cd "$(BASEDIR)/apps/ingress/" && $(MAKE) deploy
23 |
24 | reset.safe: env creds
25 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) reset.safe
26 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) reset.safe
27 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) reset.safe
28 |
29 | deploy.minikube: creds.minikube
30 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) deploy.minikube
31 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) deploy.minikube
32 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) deploy.minikube
33 | cd "$(BASEDIR)/apps/ingress/" && $(MAKE) deploy.minikube
34 |
35 | deploy.minikube.dockerhub: creds.minikube
36 | minikube addons enable ingress
37 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) deploy.minikube.dockerhub
38 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) deploy.minikube.dockerhub
39 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) deploy.minikube.dockerhub
40 | cd "$(BASEDIR)/apps/ingress/" && $(MAKE) deploy.minikube
41 | @printf -- "*** DONE ***\n"
42 | @printf -- "add the following line to your /etc/hosts file:\n\n"
43 | @printf -- "$(shell minikube ip) minikube.wap\n\n"
44 |
45 | deploy.generic:
46 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) deploy.generic
47 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) deploy.generic
48 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) deploy.generic
49 | cd "$(BASEDIR)/apps/ingress/" && $(MAKE) deploy.generic
50 |
51 | clean: env creds
52 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) clean
53 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) clean
54 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) clean
55 | cd "$(BASEDIR)/apps/ingress/" && $(MAKE) clean
56 |
57 | clean.generic:
58 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) clean.generic
59 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) clean.generic
60 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) clean.generic
61 | cd "$(BASEDIR)/apps/ingress/" && $(MAKE) clean.generic
62 |
63 | clean.minikube:
64 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) clean.minikube
65 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) clean.minikube
66 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) clean.minikube
67 | cd "$(BASEDIR)/apps/ingress/" && $(MAKE) clean.minikube
68 |
69 | clean.minikube.dockerhub:
70 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) clean.minikube
71 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) clean.minikube
72 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) clean.minikube.dockerhub
73 | cd "$(BASEDIR)/apps/ingress/" && $(MAKE) clean.minikube
74 |
75 | build: env creds
76 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) build
77 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) build
78 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) build
79 |
80 | build.dockerhub:
81 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) build.dockerhub
82 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) build.dockerhub
83 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) build.dockerhub
84 |
85 | build.generic:
86 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) build.generic
87 | cd "$(BASEDIR)/apps/game/kubernetes/" && $(MAKE) build.generic
88 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) build.generic
89 |
90 | config: env creds
91 | @cd "$(BASEDIR)/apps/ingress/" && $(MAKE) config
92 |
93 | test:
94 | cd "$(BASEDIR)/apps/api/kubernetes/" && $(MAKE) test
95 | cd "$(BASEDIR)/apps/admin/kubernetes/" && $(MAKE) test
96 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Whack-a-pod
2 | This is a demo that can be used to show how resilient services running on
3 | Kubernetes can be. Main app shows a giant sign that flashes in various random
4 | colors. Those colors come a Kubernetes powered microservice. If the service
5 | goes down, the sign turns red. Your goal is to try and knock the service down
6 | by killing the Kubernetes pods that run the service. You can do that by
7 | whacking the pods wich are respresented as moles.
8 |
9 | 
10 |
11 | There is also a less busy verison of the game available at /next.html. This
12 | version has an advanced mode that allows someone to do a more visual
13 | explanation of the mechanics.
14 |
15 | 
16 |
17 | The advanced version allows you to track the pod that is serving the color
18 | service and to simulate creating and destroying nodes.
19 |
20 | 
21 |
22 | ## Getting Started
23 |
24 | The current directions assume you are using Google Cloud Platform to take
25 | advantage of Container Engine to build a manage your Kubernetes cluster. There
26 | is nothing preventing this app from *running* on a Kubernetes cluster hosted
27 | elsewhere, but the directions for setup assume Container Engine. If there is
28 | significant interest in these directions, I'll be happy to publish them (or
29 | better yet, accept a pull request.)
30 |
31 | ### Create and configure GCP project
32 | 1. Create Project in Cloud Console
33 | 1. Navigate to Compute Engine (to activate Compute Engine service)
34 | 1. Navigate to the API Library and activate Container Builder API
35 |
36 |
37 | ### Create Configs
38 | 1. Make a copy of `/Samples.properties`, renamed to `/Makefile.properties`
39 | 1. Alter value for `PROJECT` to your project id
40 | 1. Alter `ZONE` and `REGION` if you want to run this demo in a particular area.
41 | 1. Alter `CLUSTER` if you want to call your cluster something other than
42 | `whack-a-pod`.
43 | 1. Set `INGRESSNAME` if you need to use something other than the default.
44 | 1. Set `DOCKERREPO` if you need to use something other Google Container Registry.
45 | 1. Open a terminal in `/`.
46 | 1. Run `make config` to create your ingress file.
47 | 1. This should create the following file:
48 | 1. /apps/ingress/ingress.yaml
49 |
50 |
51 | >I use this application to show off Google Cloud Platform, so I tend set it up
52 | multiple times, once per region or datacenter. Therefore, I rename the `INGRESSNAME` and
53 | `CLUSTER` a bunch. If you only have one cluster, you don't have to fiddle with
54 | these.
55 |
56 | ### Build Infrastructure
57 | 1. Open a terminal in `/infrastructure/`.
58 | 1. Run `make build`.
59 | `make build` will do the following:
60 | 1. Create Kubernetes Cluster
61 | 1. Create 1 static ip addresse for use in the app
62 |
63 | >If you get the error `ResponseError: code=503,
64 | message=Project projectname is not fully initialized with the default service
65 | accounts. Please try again later.` You need to navigfate to Compute Engine in
66 | Google Cloud console to activate Compute Engine service.
67 |
68 |
69 |
70 | ### Build Application
71 | 1. Open a terminal in root of whack_a_pod location.
72 | 1. Run `make build`
73 | 1. Run `make deploy`
74 | 1. When process finishes Browse to the the IP address value for the ingress.
75 | 1. To get that address: `gcloud compute addresses describe #INGRESSNAME# --global`
76 |
77 | ## Run demo
78 | There are two skins to the game.
79 | 1. Carnival version:
80 | * http://[gamehost]/
81 | 1. Google Cloud Next branded version:
82 | * http://[gamehost]/next.html
83 | * http://[gamehost]/advanced.html
84 |
85 | The advanced version of the game is a great demo for teaching some of the
86 | fundamentals of Kubernetes. It allows you to cordon and uncordon nodes of the
87 | Kubernetes cluster to simulate Node failure. In addition it shows which Pod of
88 | the Replica Set is actually answering calls for the service.
89 |
90 | ### Clean Up
91 | 1. Open a terminal in `/`.
92 | 1. Run `make clean`
93 | 1. Open a terminal in `/infrastructure/`.
94 | 1. Run `make clean`
95 |
96 | ## Minikube
97 | Whack a Pod can run on Minikube. Its performance isn't stellar, but the game
98 | versions of it run just as well a it does on a flaky conference wifi.
99 |
100 | ### Prerequisite
101 | * Install minikube
102 | [Directions](https://github.com/kubernetes/minikube/releases)
103 | * Enable ingress
104 | `minikube addons enable ingress`
105 | * Install xhyve driver (Mac OS])
106 | [Directions](https://github.com/kubernetes/minikube/blob/master/docs/drivers.md)
107 |
108 | ### Docker Repository
109 | You can use the Container Registry based commands in the Makefiles to build and
110 | host your Docker images.
111 |
112 | 1. Open a terminal in root of whack_a_pod location.
113 | 1. Run `make build`
114 | 1. Make images publicly available by following [these directions](https://cloud.google.com/container-registry/docs/access-control)
115 |
116 | This still requires a Google Cloud Platform Project. If you would like to build
117 | them some other way, you can, nothing restricts you from doing so. Just make
118 | sure you set `$(DOCKERREPO)` to the right value in Makefile.properties.
119 |
120 | #### Docker Repository with DockerHub
121 | Alternatively, to host your images on DockerHub,
122 |
123 | 1. Create an account on DockerHub
124 | 2. Create an image repository on DockerHub
125 | 3. In the `Makefile.properties`, change the value of `PROJECT` so that it equals your image repository slug
126 | 4. In the `Makefile.properties`, change the value of `DOCKERREPO` so that it looks like `yourusername/$(PROJECT)`
127 | 5. Run `make build.dockerhub`
128 |
129 | Your repository should have three new images with the tags:
130 | - admin
131 | - api
132 | - game
133 |
134 | For example, if your username is `username` and your repository name is `wap`, your images will be pushed to:
135 | - username/wap:admin
136 | - username/wap:api
137 | - username/wap:game
138 |
139 | ### Running on Minikube
140 |
141 | 1. Open a terminal in root of whack_a_pod location.
142 | 1. Run `minikube start --vm-driver=xhyve`
143 | 1. Run `make deploy.minikube`
144 | 1. Run `minikube ip` to get the IP address of the Minikube deployment.
145 | 1. Create an entry in /etc/hosts pointing IP address to `minikube.wap`.
146 |
147 | ### Running on Minikube with DockerHub
148 | 1. Open a terminal in root of whack_a_pod location.
149 | 1. Run `minikube start`
150 | 1. Run `make deploy.minikube.dockerhub`
151 | 1. The last line of the previous command should contain an IP address beside a `"minikube.wap"` string
152 | 1. Run `sudo vim /etc/hosts`, append the last line to the `/etc/hosts` and save the file.
153 | 1. Wait for the ingress to obtain an IP address (run `kubectl get ing --watch` and wait till the `ADDRESS` column has a value)
154 | 1. Visit [http://minikube.wap](http://minikube.wap) in the browser
155 |
156 | ### Clean Minikube
157 | 1. Run `make clean.minikube`
158 | 1. Run `minikube stop`
159 |
160 | ### Running on any Kubernetes (generic)
161 |
162 | This method is for generic usage and can be run on any Kubernetes installation. There are few differences:
163 | * It will push built images to repo `DOCKERREPO` defined in Makefile.properties
164 | * It's agnostic of any loadbalancer in front of ingress so you can use `NodePort` type for ingress service
165 | * It works with RBAC model - proper serviceaccount and role bindings are created
166 | * It will deploy all objects in current namespace
167 |
168 | 1. Open a terminal in root of whack_a_pod location.
169 | 1. Build application with `make build.generic` **OR** skip building by setting `DOCKERREPO` to **cloudowski** and use prebuilt images availabe on dockerhub
170 | 1. Run `make deploy.generic`
171 | 1. Define name `whackapod.example.com` in your `/etc/hosts` pointing to IP address of your load balancer in front of ingress controller or one of nodes IP (when using `NodePort`)
172 | 1. Open your browser at [http://whackapod.example.com/](http://whackapod.example.com)
173 |
174 | ### Clean generic deployment
175 | 1. Run `make clean.generic`
176 |
177 | ## Architecture
178 | There are three Kubernetes services that make up the whole application:
179 | 1. Game
180 | Game contains all of the front end clients for the game, both the carnival
181 | version and the Google Cloud Next version.
182 | 1. Admin
183 | Admin contains all of the logic for managing the whole application. This is
184 | the application the front end calls to get a list of the pods running the
185 | color api, it also has calls to create and delete deployments, delete pods, and
186 | drain and uncordon nodes.
187 | 1. Api
188 | Api contains two service calls: color and color-complete. Color is a random
189 | hexidecimal RGB color value. Color-complete is the same as color, but also
190 | sends the pod name of the pod that answered the service call.
191 |
192 |
193 | "This is not an official Google Project."
194 |
--------------------------------------------------------------------------------
/Sample.properties:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | PROJECT=[your project]
15 | ZONE=us-central1-c
16 | REGION=us-central1
17 | CLUSTER=whack-a-pod
18 | INGRESSNAME=whack-a-pod-ingress
19 | DOCKERREPO=gcr.io/$(PROJECT)
20 |
21 | creds:
22 | gcloud container clusters get-credentials $(CLUSTER)
23 |
24 | env:
25 | gcloud config set project $(PROJECT)
26 | gcloud config set compute/zone $(ZONE)
27 |
28 | creds.minikube:
29 | kubectl config use-context minikube
--------------------------------------------------------------------------------
/apps/admin/containers/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | FROM scratch
15 | ADD main /
16 | CMD ["/main"]
--------------------------------------------------------------------------------
/apps/admin/containers/kubernetes.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | // See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | package main
12 |
13 | import (
14 | "bytes"
15 | "encoding/json"
16 | "fmt"
17 | "io/ioutil"
18 | "net/http"
19 | "os"
20 | "strconv"
21 | )
22 |
23 | type httpClient interface {
24 | Do(req *http.Request) (*http.Response, error)
25 | }
26 |
27 | func queryK8sAPI(url, method string, data []byte) ([]byte, int, error) {
28 | req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
29 | if err != nil {
30 | return nil, http.StatusInternalServerError, fmt.Errorf("could not create request for HTTP %s %s: %v", method, url, err)
31 | }
32 | // This is required for k8s api calls.
33 | req.Header.Add("Authorization", "Bearer "+token)
34 |
35 | if method == http.MethodPost {
36 | req.Header.Set("Content-Type", "application/json")
37 | req.Header.Set("Content-Length", strconv.Itoa(len(string(data))))
38 | }
39 |
40 | if method == http.MethodPatch {
41 | req.Header.Set("Content-Type", "application/merge-patch+json")
42 | req.Header.Set("Content-Length", strconv.Itoa(len(string(data))))
43 | }
44 |
45 | resp, err := client.Do(req)
46 | if err != nil {
47 | return nil, http.StatusInternalServerError, fmt.Errorf("could not execute HTTP request for HTTP %s %s: %v", method, url, err)
48 | }
49 | defer resp.Body.Close()
50 |
51 | b, err := ioutil.ReadAll(resp.Body)
52 | if err != nil {
53 | return nil, http.StatusInternalServerError, fmt.Errorf("could not read HTTP request for HTTP %s %s: %v", method, url, err)
54 | }
55 | return b, resp.StatusCode, nil
56 | }
57 |
58 | func listPods() ([]byte, error) {
59 | url := root + "/api/v1/pods?labelSelector=" + selector
60 |
61 | b, _, err := queryK8sAPI(url, "GET", nil)
62 | if err != nil {
63 | return nil, fmt.Errorf("can't list pods: %v", err)
64 | }
65 |
66 | return b, nil
67 | }
68 |
69 | func deletePod(podname string) ([]byte, error) {
70 | url := root + podname
71 |
72 | b, status, err := queryK8sAPI(url, "DELETE", nil)
73 | if err != nil {
74 | return nil, fmt.Errorf("can't delete pod: %v", err)
75 | }
76 |
77 | if status == http.StatusNotFound {
78 | return nil, errItemNotExist
79 | }
80 |
81 | return b, nil
82 |
83 | }
84 |
85 | func deletePods(node string) ([]byte, error) {
86 | url := root + "/api/v1/namespaces/default/pods" + "?labelSelector=" + selector
87 | if len(node) > 0 {
88 | fs := "&fieldSelector=spec.nodeName=" + node
89 | url += fs
90 | }
91 |
92 | b, status, err := queryK8sAPI(url, "DELETE", nil)
93 | if err != nil {
94 | return nil, fmt.Errorf("can't delete pods: %v", err)
95 | }
96 |
97 | if status == http.StatusNotFound {
98 | return nil, errItemNotExist
99 | }
100 |
101 | return b, nil
102 |
103 | }
104 |
105 | func describePod(podname string) ([]byte, error) {
106 | url := root + podname
107 |
108 | b, _, err := queryK8sAPI(url, "GET", nil)
109 | if err != nil {
110 | return nil, fmt.Errorf("can't describe pod: %v", err)
111 | }
112 |
113 | return b, nil
114 |
115 | }
116 |
117 | func listNodes() ([]byte, error) {
118 | url := root + "/api/v1/nodes"
119 |
120 | b, _, err := queryK8sAPI(url, "GET", nil)
121 | if err != nil {
122 | return nil, fmt.Errorf("can't list nodes: %v", err)
123 | }
124 |
125 | return b, nil
126 | }
127 |
128 | func toggleNode(nodename string, inactive bool) ([]byte, error) {
129 | url := root + "/api/v1/nodes/" + nodename
130 |
131 | j := fmt.Sprintf("{\"spec\": {\"unschedulable\": %t}}", inactive)
132 | b, status, err := queryK8sAPI(url, "PATCH", []byte(j))
133 | if err != nil {
134 | return nil, fmt.Errorf("can't toggle node: %s inactive: %t %v", nodename, inactive, err)
135 | }
136 |
137 | if status == http.StatusNotFound {
138 | return nil, errItemNotExist
139 | }
140 |
141 | return b, nil
142 | }
143 |
144 | func deleteReplicaSet() ([]byte, error) {
145 | url := root + "/apis/extensions/v1beta1/namespaces/default/replicasets" + "?labelSelector=" + selector
146 |
147 | b, status, err := queryK8sAPI(url, "DELETE", nil)
148 | if err != nil {
149 | return nil, fmt.Errorf("can't delete replica set: %v", err)
150 | }
151 |
152 | if status == http.StatusNotFound {
153 |
154 | return nil, errItemNotExist
155 | }
156 |
157 | return b, nil
158 |
159 | }
160 |
161 | type minimumDeployment struct {
162 | APIVersion string `json:"apiVersion,omitempty"`
163 | Kind string `json:"kind,omitempty"`
164 | Metadata struct {
165 | Name string `json:"name,omitempty"`
166 | } `json:"metadata,omitempty"`
167 | Spec struct {
168 | Replicas int `json:"replicas,omitempty"`
169 | Selector struct {
170 | MatchLabels map[string]string `json:"matchLabels,omitempty"`
171 | } `json:"selector,omitempty"`
172 | Strategy struct {
173 | Type string `json:"type,omitempty"`
174 | } `json:"strategy,omitempty"`
175 | Template struct {
176 | Metadata struct {
177 | Labels map[string]string `json:"labels,omitempty"`
178 | } `json:"metadata,omitempty"`
179 | Spec struct {
180 | TerminationGracePeriodSeconds int `json:"terminationGracePeriodSeconds,omitempty"`
181 | Containers []minimumContainer `json:"containers,omitempty"`
182 | } `json:"spec,omitempty"`
183 | } `json:"template,omitempty"`
184 | } `json:"spec,omitempty"`
185 | }
186 |
187 | type minimumContainer struct {
188 | Image string `json:"image,omitempty"`
189 | ImagePullPolicy string `json:"imagePullPolicy,omitempty"`
190 | Name string `json:"name,omitempty"`
191 | Ports []minimumPort `json:"ports,omitempty"`
192 | }
193 |
194 | type minimumPort struct {
195 | ContainerPort int `json:"containerPort,omitempty"`
196 | Name string `json:"name,omitempty"`
197 | Protocol string `json:"protocol,omitempty"`
198 | }
199 |
200 | func createDeployment() ([]byte, error) {
201 | selflink := "/apis/extensions/v1beta1/namespaces/default/deployments"
202 | url := root + selflink
203 |
204 | image := os.Getenv("APIIMAGE")
205 | if len(image) == 0 {
206 | return nil, fmt.Errorf("env var APIIMAGE not set")
207 | }
208 |
209 | policy := os.Getenv("APIPULLPOLICY")
210 | if len(policy) == 0 {
211 | policy = "IfNotPresent"
212 | }
213 |
214 | var d minimumDeployment
215 | d.APIVersion = "extensions/v1beta1"
216 | d.Kind = "Deployment"
217 | d.Metadata.Name = "api-deployment"
218 | d.Spec.Replicas = 12
219 | d.Spec.Selector.MatchLabels = map[string]string{"app": "api"}
220 | d.Spec.Strategy.Type = "RollingUpdate"
221 | d.Spec.Template.Metadata.Labels = map[string]string{"app": "api"}
222 | d.Spec.Template.Spec.TerminationGracePeriodSeconds = 1
223 | d.Spec.Template.Spec.Containers = []minimumContainer{
224 | minimumContainer{
225 | Name: "api",
226 | Image: image,
227 | ImagePullPolicy: policy,
228 |
229 | Ports: []minimumPort{
230 | minimumPort{
231 | ContainerPort: 8080,
232 | Name: "http",
233 | Protocol: "TCP",
234 | },
235 | },
236 | },
237 | }
238 |
239 | dbytes, err := json.Marshal(d)
240 | if err != nil {
241 | return nil, fmt.Errorf("could not convert deployment to json: %v", err)
242 | }
243 |
244 | b, status, err := queryK8sAPI(url, "POST", dbytes)
245 | if err != nil {
246 | return nil, fmt.Errorf("can't created deployment: %v", err)
247 | }
248 |
249 | if status == http.StatusNotFound {
250 | return nil, errItemNotExist
251 | }
252 |
253 | if status == http.StatusConflict {
254 | return nil, errItemAlreadyExist
255 | }
256 |
257 | return b, nil
258 |
259 | }
260 |
261 | func deleteDeployment(depname string) ([]byte, error) {
262 | selflink := "/apis/extensions/v1beta1/namespaces/default/deployments/" + depname
263 | url := root + selflink
264 |
265 | b, status, err := queryK8sAPI(url, "DELETE", nil)
266 | if err != nil {
267 | return nil, fmt.Errorf("can't delete deployment: %v", err)
268 | }
269 |
270 | if status == http.StatusNotFound {
271 | return nil, errItemNotExist
272 | }
273 |
274 | return b, nil
275 |
276 | }
277 |
--------------------------------------------------------------------------------
/apps/admin/containers/kubernetes_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | // See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | package main
12 |
13 | import (
14 | "bufio"
15 | "encoding/json"
16 | "fmt"
17 | "io/ioutil"
18 | "net/http"
19 | "os"
20 | "strings"
21 | "testing"
22 |
23 | "k8s.io/api/apps/v1beta1"
24 | "k8s.io/api/core/v1"
25 | )
26 |
27 | var (
28 | deploymentPath = "/apis/extensions/v1beta1/namespaces/default/deployments"
29 | deploymentName = "api-deployment"
30 | nodePath = "/api/v1/nodes"
31 | nodeName = "gke-whack-a-pod-default-pool-8deaa3a5-b9p7"
32 | podDeletePath = "/api/v1/namespaces/default/pods"
33 | podExistsName = "api-deployment-1435701907-xx9lm"
34 | podExistsSelfLink = "/api/v1/namespaces/default/pods/api-deployment-1435701907-xx9lm"
35 | podListPath = "/api/v1/pods"
36 | )
37 |
38 | func TestCreateDeploymentNoEnv(t *testing.T) {
39 | client = new(MockClient)
40 | _, err := createDeployment()
41 | if err == nil {
42 | t.Errorf("Expected error due to no ENV variable")
43 | }
44 | }
45 |
46 | func TestCreateDeployment(t *testing.T) {
47 | client = new(MockClient)
48 | os.Setenv("APIIMAGE", "gcr.io/carnivaldemos/api")
49 | b, err := createDeployment()
50 | if err != nil {
51 | t.Errorf("Error getting deployment : %v", err)
52 | }
53 | var dep v1beta1.Deployment
54 | if err := json.Unmarshal(b, &dep); err != nil {
55 | t.Errorf("error turning response to deployment: %v", err)
56 | t.FailNow()
57 | }
58 |
59 | if dep.Name != "api-deployment" {
60 | t.Errorf("createDeployment().Name got: %s, wanted: %s", dep.Name, "api-deplyment")
61 | }
62 | os.Unsetenv("APIIMAGE")
63 | }
64 |
65 | func TestDeleteDeployment(t *testing.T) {
66 | client = new(MockClient)
67 | _, err := deleteDeployment("api-deployment")
68 | if err != nil {
69 | t.Errorf("Error getting deployment : %v", err)
70 | }
71 | }
72 |
73 | func TestToggleNode(t *testing.T) {
74 |
75 | cases := []struct {
76 | node string
77 | inactive bool
78 | }{
79 | {"gke-whack-a-pod-default-pool-8deaa3a5-b9p7", true},
80 | {"gke-whack-a-pod-default-pool-8deaa3a5-b9p7", false},
81 | }
82 |
83 | client = new(MockClient)
84 |
85 | for _, c := range cases {
86 | b, err := toggleNode(c.node, c.inactive)
87 | if err != nil {
88 | t.Errorf("Error getting deployment : %v", err)
89 | }
90 |
91 | var node v1.Node
92 | if err := json.Unmarshal(b, &node); err != nil {
93 | t.Errorf("error turning response to node: %v", err)
94 | t.FailNow()
95 | }
96 |
97 | if node.Spec.Unschedulable != c.inactive {
98 | t.Errorf("toggleNode().Spec.Unschedulable got %t wanted %t", node.Spec.Unschedulable, c.inactive)
99 | }
100 |
101 | }
102 |
103 | }
104 |
105 | func TestListNodes(t *testing.T) {
106 |
107 | client = new(MockClient)
108 |
109 | b, err := listNodes()
110 | if err != nil {
111 | t.Errorf("Error getting deployment : %v", err)
112 | }
113 |
114 | var nodes v1.NodeList
115 | if err := json.Unmarshal(b, &nodes); err != nil {
116 | t.Errorf("error turning response to nodelist: %v", err)
117 | t.FailNow()
118 | }
119 |
120 | if len(nodes.Items) != 2 {
121 | t.Errorf("Wanted 2 nodes got : %d", len(nodes.Items))
122 | }
123 |
124 | }
125 |
126 | func TestListPods(t *testing.T) {
127 |
128 | client = new(MockClient)
129 |
130 | b, err := listPods()
131 | if err != nil {
132 | t.Errorf("Error getting deployment : %v", err)
133 | }
134 |
135 | var pods v1.PodList
136 | if err := json.Unmarshal(b, &pods); err != nil {
137 | t.Errorf("error turning response to podlist: %v", err)
138 | t.FailNow()
139 | }
140 |
141 | if len(pods.Items) != 12 {
142 | t.Errorf("listPods(): got %d wanted : %d", len(pods.Items), 12)
143 | }
144 |
145 | }
146 |
147 | func TestDeletePods(t *testing.T) {
148 |
149 | client = new(MockClient)
150 |
151 | b, err := deletePods("")
152 | if err != nil {
153 | t.Errorf("Error getting deployment : %v", err)
154 | }
155 |
156 | var pods v1.PodList
157 | if err := json.Unmarshal(b, &pods); err != nil {
158 | t.Errorf("error turning response to podlist: %v", err)
159 | t.FailNow()
160 | }
161 |
162 | if len(pods.Items) != 12 {
163 | t.Errorf("Wanted 12 pods got : %d", len(pods.Items))
164 | }
165 |
166 | }
167 |
168 | func TestDescribePod(t *testing.T) {
169 |
170 | client = new(MockClient)
171 | self := "/api/v1/namespaces/default/pods/api-deployment-1435701907-xx9lm"
172 |
173 | b, err := describePod(self)
174 | if err != nil {
175 | t.Errorf("Error getting pod : %v", err)
176 | }
177 |
178 | var pod v1.Pod
179 | if err := json.Unmarshal(b, &pod); err != nil {
180 | t.Errorf("error turning response to podlist: %v", err)
181 | t.FailNow()
182 | }
183 |
184 | if pod.ObjectMeta.SelfLink != self {
185 | t.Errorf("Selflink should be %s got : %s", pod.ObjectMeta.SelfLink, self)
186 | }
187 |
188 | }
189 |
190 | func TestDeletePod(t *testing.T) {
191 |
192 | client = new(MockClient)
193 | self := "/api/v1/namespaces/default/pods/api-deployment-1435701907-xx9lm"
194 |
195 | b, err := deletePod(self)
196 | if err != nil {
197 | t.Errorf("Unexpected error getting pod, %v", err)
198 | }
199 |
200 | var pod v1.Pod
201 | if err := json.Unmarshal(b, &pod); err != nil {
202 | t.Errorf("error turning response to podlist: %v", err)
203 | t.FailNow()
204 | }
205 |
206 | if pod.ObjectMeta.SelfLink != self {
207 | t.Errorf("Selflink should be %s got : %s", pod.ObjectMeta.SelfLink, self)
208 | }
209 |
210 | }
211 |
212 | func TestDeletePodDoesNotExist(t *testing.T) {
213 | client = new(MockClient)
214 | self := "/api/v1/namespaces/default/pods/api-deployment-1435701907-FALSE"
215 |
216 | _, err := deletePod(self)
217 | if err != errItemNotExist {
218 | t.Errorf("Unexpected error gerring pod, got %v wanted: %v", err, errItemNotExist)
219 | }
220 |
221 | }
222 |
223 | type MockClient struct {
224 | DoFunc func(req *http.Request) (*http.Response, error)
225 | }
226 |
227 | func (m *MockClient) Do(req *http.Request) (*http.Response, error) {
228 |
229 | r := http.Response{}
230 | r.StatusCode = http.StatusOK
231 | r.Request = req
232 | filename := ""
233 |
234 | switch {
235 |
236 | case req.URL.Path == deploymentPath:
237 | filename = "testdata/deployment_create.json"
238 |
239 | case req.URL.Path == deploymentPath+"/"+deploymentName:
240 | filename = "testdata/deployment_delete.json"
241 |
242 | case req.URL.Path == nodePath:
243 | filename = "testdata/nodes.json"
244 |
245 | case req.URL.Path == podDeletePath+"/"+podExistsName:
246 | filename = "testdata/pod.json"
247 |
248 | case req.URL.Path == podListPath && req.FormValue("labelSelector") == selector:
249 | filename = "testdata/pods.json"
250 |
251 | case req.URL.Path == podDeletePath && req.FormValue("labelSelector") == selector && req.FormValue("fieldSelector") == "spec.nodeName="+nodeName:
252 | filename = "testdata/pods_onenode_only.json"
253 |
254 | case req.URL.Path == podDeletePath && req.FormValue("labelSelector") == selector:
255 | filename = "testdata/pods.json"
256 |
257 | case req.URL.Path == nodePath+"/"+nodeName:
258 | b, err := ioutil.ReadAll(req.Body)
259 | if err != nil {
260 | return nil, fmt.Errorf("could not read request data : %v", err)
261 | }
262 |
263 | filename = "testdata/node_schedulable.json"
264 | if strings.Index(string(b), "true") >= 0 {
265 | filename = "testdata/node_unschedulable.json"
266 | }
267 | default:
268 | filename = "testdata/empty.json"
269 | r.StatusCode = http.StatusNotFound
270 | }
271 |
272 | f, err := os.Open(filename)
273 | if err != nil {
274 | return nil, fmt.Errorf("could not get cached file (%s) for test: %v", filename, err)
275 | }
276 | reader := bufio.NewReader(f)
277 | r.Body = ioutil.NopCloser(reader)
278 |
279 | return &r, nil
280 |
281 | }
282 |
--------------------------------------------------------------------------------
/apps/admin/containers/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | // See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | // Package main is a Kubernetes API proxy. It exposes a smaller surface of the
12 | // API and limits operations to specifically selected labels, and deployments
13 | package main
14 |
15 | import (
16 | "crypto/tls"
17 | "crypto/x509"
18 | "fmt"
19 | "io/ioutil"
20 | "log"
21 | "net/http"
22 | "strings"
23 | "time"
24 |
25 | "github.com/gorilla/mux"
26 | )
27 |
28 | var (
29 | client httpClient
30 | pool *x509.CertPool
31 | token = ""
32 | errItemNotExist = fmt.Errorf("Item does not exist")
33 | errItemAlreadyExist = fmt.Errorf("Item already exists")
34 | )
35 |
36 | const (
37 | root = "https://kubernetes"
38 | selector = "app=api"
39 | defaultTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
40 | defaultCertPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
41 | )
42 |
43 | func main() {
44 | log.Printf("starting whack a pod admin api")
45 | var err error
46 |
47 | b, err := ioutil.ReadFile(defaultTokenPath)
48 | if err != nil {
49 | log.Printf("could not get token from file system: %v", err)
50 | }
51 | token = string(b)
52 |
53 | certs, err := ioutil.ReadFile(defaultCertPath)
54 | if err != nil {
55 | log.Printf("could not get token from file system: %v", err)
56 | }
57 |
58 | // This allows me to use a scratch Dockerfile as described here :
59 | // https://medium.com/@kelseyhightower/optimizing-docker-images-for-static-binaries-b5696e26eb07
60 | // But instead of using the Authoritative Certs from a linux install, I'm
61 | // using the certs mounted from the Kuberntes server itself. Since all
62 | // this client does is talk to the Kubernetes server, this should always be
63 | // up to date.
64 | pool = x509.NewCertPool()
65 | pool.AppendCertsFromPEM(certs)
66 | client = &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: pool}}}
67 |
68 | router := mux.NewRouter()
69 | for _, r := range routes {
70 | router.Methods(r.method).Path(r.pattern).Handler(r.handlerFunc)
71 | // Couldn't get a regex working here and wrote this to make it stop
72 | // eating hours of my life.
73 | if strings.Index(r.pattern, "/k8s/") == 0 {
74 | router.Methods(r.method).Path("/admin" + r.pattern).Handler(r.handlerFunc)
75 | }
76 | }
77 |
78 | srv := &http.Server{
79 | ReadTimeout: 5 * time.Second,
80 | WriteTimeout: 10 * time.Second,
81 | Addr: ":8080",
82 | Handler: router,
83 | }
84 |
85 | srv.ListenAndServe()
86 |
87 | }
88 |
89 | // Route represets a gorrila route from a http call to an application function
90 | type route struct {
91 | method string
92 | pattern string
93 | handlerFunc http.HandlerFunc
94 | }
95 |
96 | var routes = []route{
97 | {"GET", "/", health},
98 | {"GET", "/healthz", health},
99 | {"GET", "/k8s/pods/get", handleAPI(handlePods)},
100 | {"GET", "/k8s/nodes/get", handleAPI(handleNodes)},
101 | {"GET", "/k8s/pod/delete", handleAPI(handlePodDelete)},
102 | {"GET", "/k8s/pods/delete", handleAPI(handlePodsDelete)},
103 | {"GET", "/k8s/node/drain", handleAPI(handleNodeDrain)},
104 | {"GET", "/k8s/node/uncordon", handleAPI(handleNodeUncordon)},
105 | {"GET", "/k8s/deployment/delete", handleAPI(handleDeploymentDelete)},
106 | {"GET", "/k8s/deployment/create", handleAPI(handleDeploymentCreate)},
107 | }
108 |
109 | func health(w http.ResponseWriter, r *http.Request) {
110 | r.Close = true
111 | w.WriteHeader(http.StatusOK)
112 | fmt.Fprint(w, "ok")
113 | }
114 |
115 | type apiHandler func(http.ResponseWriter, *http.Request) ([]byte, error)
116 |
117 | func handleAPI(h apiHandler) http.HandlerFunc {
118 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
119 | h.ServeHTTP(w, r)
120 | })
121 | }
122 |
123 | func (h apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
124 | r.Close = true
125 | w.Header().Add("Access-Control-Allow-Origin", "*")
126 | b, err := h(w, r)
127 | status := http.StatusOK
128 | if err != nil {
129 | status = http.StatusInternalServerError
130 | if err == errItemNotExist {
131 | status = http.StatusAccepted
132 | }
133 |
134 | if err == errItemAlreadyExist {
135 | status = http.StatusAccepted
136 | }
137 |
138 | sendJSON(w, fmt.Sprintf("{\"error\":\"%v\"}", err), status)
139 | log.Printf("%s %d %s", r.Method, status, r.URL)
140 | log.Printf("Error %v", err)
141 | return
142 | }
143 | sendJSON(w, string(b), status)
144 | log.Printf("%s %d %s", r.Method, status, r.URL)
145 | }
146 |
147 | func handlePods(w http.ResponseWriter, r *http.Request) ([]byte, error) {
148 | return listPods()
149 | }
150 |
151 | func handlePodDelete(w http.ResponseWriter, r *http.Request) ([]byte, error) {
152 | b, err := deletePod(r.FormValue("pod"))
153 | if err != nil {
154 | if err == errItemNotExist {
155 | return nil, errItemNotExist
156 | }
157 |
158 | return nil, fmt.Errorf("could not delete k8s object: %v", err)
159 | }
160 |
161 | return b, nil
162 | }
163 |
164 | func handlePodsDelete(w http.ResponseWriter, r *http.Request) ([]byte, error) {
165 |
166 | b, err := deletePods("")
167 | if err != nil && err != errItemNotExist {
168 | return nil, fmt.Errorf("could not delete k8s pods: %v", err)
169 | }
170 | return b, nil
171 | }
172 |
173 | func handleDeploymentCreate(w http.ResponseWriter, r *http.Request) ([]byte, error) {
174 | return createDeployment()
175 | }
176 |
177 | func handleDeploymentDelete(w http.ResponseWriter, r *http.Request) ([]byte, error) {
178 |
179 | _, err := deleteDeployment("api-deployment")
180 | if err != nil && err != errItemNotExist {
181 | return nil, fmt.Errorf("could not delete k8s deployment: %v", err)
182 | }
183 |
184 | _, err = deleteReplicaSet()
185 | if err != nil && err != errItemNotExist {
186 | return nil, fmt.Errorf("could not delete k8s replica set: %v", err)
187 | }
188 |
189 | return handlePodsDelete(w, r)
190 | }
191 |
192 | func handleNodes(w http.ResponseWriter, r *http.Request) ([]byte, error) {
193 | return listNodes()
194 | }
195 |
196 | func handleNodeDrain(w http.ResponseWriter, r *http.Request) ([]byte, error) {
197 | nodename := r.FormValue("node")
198 |
199 | b, err := toggleNode(nodename, true)
200 | if err != nil && err != errItemNotExist {
201 | return nil, fmt.Errorf("could not retrieve k8s node info: %v", err)
202 | }
203 |
204 | _, err = deletePods(nodename)
205 | if err != nil {
206 | return nil, fmt.Errorf("could not remove all pods on node: %v", err)
207 | }
208 |
209 | return b, nil
210 | }
211 |
212 | func handleNodeUncordon(w http.ResponseWriter, r *http.Request) ([]byte, error) {
213 |
214 | b, err := toggleNode(r.FormValue("node"), false)
215 | if err != nil && err != errItemNotExist {
216 | return nil, fmt.Errorf("could uncordon node : %v", err)
217 | }
218 |
219 | return b, nil
220 | }
221 |
222 | func sendJSON(w http.ResponseWriter, content string, status int) {
223 | w.Header().Set("Content-Type", "application/json")
224 | w.WriteHeader(status)
225 | fmt.Fprint(w, content)
226 | }
227 |
--------------------------------------------------------------------------------
/apps/admin/containers/main_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All Rights Reserved.
2 | // Licensed under the Apache License, Version 2.0 (the "License");
3 | // you may not use this file except in compliance with the License.
4 | // You may obtain a copy of the License at
5 | // http://www.apache.org/licenses/LICENSE-2.0
6 | // Unless required by applicable law or agreed to in writing, software
7 | // distributed under the License is distributed on an "AS IS" BASIS,
8 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9 | // See the License for the specific language governing permissions and
10 | // limitations under the License.
11 | package main
12 |
13 | import (
14 | "encoding/json"
15 | "io/ioutil"
16 | "log"
17 | "net/http"
18 | "net/http/httptest"
19 | "os"
20 | "testing"
21 |
22 | "k8s.io/api/apps/v1beta2"
23 | "k8s.io/api/core/v1"
24 | )
25 |
26 | func TestHealth(t *testing.T) {
27 | log.SetOutput(ioutil.Discard)
28 | req, err := http.NewRequest("GET", "/healthz", nil)
29 | if err != nil {
30 | t.Fatal(err)
31 | }
32 |
33 | rr := httptest.NewRecorder()
34 | handler := http.HandlerFunc(health)
35 | handler.ServeHTTP(rr, req)
36 |
37 | if status := rr.Code; status != http.StatusOK {
38 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
39 | }
40 |
41 | expected := `ok`
42 | if rr.Body.String() != expected {
43 | t.Errorf("unexpected body: got %v want %v", rr.Body.String(), expected)
44 | }
45 | }
46 |
47 | func TestHandlePods(t *testing.T) {
48 | log.SetOutput(ioutil.Discard)
49 | client = new(MockClient)
50 | req, err := http.NewRequest("GET", "/admin/k8s/pods/get", nil)
51 | if err != nil {
52 | t.Fatal(err)
53 | }
54 |
55 | rr := httptest.NewRecorder()
56 | handler := http.HandlerFunc(handleAPI(handlePods))
57 | handler.ServeHTTP(rr, req)
58 |
59 | if status := rr.Code; status != http.StatusOK {
60 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
61 | }
62 |
63 | var podlist v1.PodList
64 | if err := json.Unmarshal([]byte(rr.Body.String()), &podlist); err != nil {
65 | t.Errorf("error turning response to podlist: %v", err)
66 | t.FailNow()
67 | }
68 |
69 | if len(podlist.Items) < 12 {
70 | t.Errorf("podlist.Items: got %d want %d", len(podlist.Items), 12)
71 | }
72 |
73 | }
74 |
75 | func TestHandlePodDeleteExisting(t *testing.T) {
76 | log.SetOutput(ioutil.Discard)
77 | client = new(MockClient)
78 | req, err := http.NewRequest("GET", "/admin/k8s/pod/delete?pod="+podExistsSelfLink, nil)
79 | if err != nil {
80 | t.Fatal(err)
81 | }
82 |
83 | rr := httptest.NewRecorder()
84 | handler := http.HandlerFunc(handleAPI(handlePodDelete))
85 | handler.ServeHTTP(rr, req)
86 |
87 | if status := rr.Code; status != http.StatusOK {
88 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
89 | }
90 |
91 | var pod v1.Pod
92 | if err := json.Unmarshal([]byte(rr.Body.String()), &pod); err != nil {
93 | t.Errorf("error turning response to pod: %v", err)
94 | t.FailNow()
95 | }
96 |
97 | if pod.SelfLink != podExistsSelfLink {
98 | t.Errorf("podlist.SelfLink: got %s want %s", pod.SelfLink, podExistsSelfLink)
99 | }
100 |
101 | }
102 |
103 | func TestHandlePodDeleteNonExisting(t *testing.T) {
104 | log.SetOutput(ioutil.Discard)
105 | client = new(MockClient)
106 | req, err := http.NewRequest("GET", "/admin/k8s/pod/delete?pod=dsadasdasdsa", nil)
107 | if err != nil {
108 | t.Fatal(err)
109 | }
110 |
111 | rr := httptest.NewRecorder()
112 | handler := http.HandlerFunc(handleAPI(handlePodDelete))
113 | handler.ServeHTTP(rr, req)
114 |
115 | if status := rr.Code; status != http.StatusAccepted {
116 | t.Errorf("wrong status code: got %v want %v", status, http.StatusAccepted)
117 | }
118 |
119 | }
120 |
121 | func TestHandlePodsDeleteAll(t *testing.T) {
122 | log.SetOutput(ioutil.Discard)
123 | client = new(MockClient)
124 | req, err := http.NewRequest("GET", "/admin/k8s/pods/delete", nil)
125 | if err != nil {
126 | t.Fatal(err)
127 | }
128 |
129 | rr := httptest.NewRecorder()
130 | handler := http.HandlerFunc(handleAPI(handlePodsDelete))
131 | handler.ServeHTTP(rr, req)
132 |
133 | if status := rr.Code; status != http.StatusOK {
134 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
135 | }
136 |
137 | var podlist v1.PodList
138 | if err := json.Unmarshal([]byte(rr.Body.String()), &podlist); err != nil {
139 | t.Errorf("error turning response to pod: %v", err)
140 | t.FailNow()
141 | }
142 |
143 | if len(podlist.Items) != 12 {
144 | t.Errorf("len(podlist.Items): got %d want %d", len(podlist.Items), 12)
145 | }
146 |
147 | }
148 |
149 | func TestHandleDeploymentCreate(t *testing.T) {
150 | log.SetOutput(ioutil.Discard)
151 | client = new(MockClient)
152 | os.Setenv("APIIMAGE", "gcr.io/carnivaldemos/api")
153 | req, err := http.NewRequest("GET", "/admin/k8s/deployment/create", nil)
154 | if err != nil {
155 | t.Fatal(err)
156 | }
157 |
158 | rr := httptest.NewRecorder()
159 | handler := http.HandlerFunc(handleAPI(handleDeploymentCreate))
160 | handler.ServeHTTP(rr, req)
161 |
162 | if status := rr.Code; status != http.StatusOK {
163 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
164 | }
165 |
166 | var d v1beta2.Deployment
167 | if err := json.Unmarshal([]byte(rr.Body.String()), &d); err != nil {
168 | t.Errorf("error turning response to deployment: %v", err)
169 | t.FailNow()
170 | }
171 |
172 | if d.Name != deploymentName {
173 | t.Errorf("Deployment Name: got %s want %s", d.Name, deploymentName)
174 | }
175 | os.Unsetenv("APIIMAGE")
176 | }
177 |
178 | func TestHandleDeploymentDelete(t *testing.T) {
179 | log.SetOutput(ioutil.Discard)
180 | client = new(MockClient)
181 | req, err := http.NewRequest("GET", "/admin/k8s/deployment/delete", nil)
182 | if err != nil {
183 | t.Fatal(err)
184 | }
185 |
186 | rr := httptest.NewRecorder()
187 | handler := http.HandlerFunc(handleAPI(handleDeploymentDelete))
188 | handler.ServeHTTP(rr, req)
189 |
190 | if status := rr.Code; status != http.StatusOK {
191 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
192 | }
193 |
194 | var podlist v1.PodList
195 | if err := json.Unmarshal([]byte(rr.Body.String()), &podlist); err != nil {
196 | t.Errorf("error turning response to pod: %v", err)
197 | t.FailNow()
198 | }
199 |
200 | if len(podlist.Items) < 12 {
201 | t.Errorf("podlist.Items: got %d want %d", len(podlist.Items), 12)
202 | }
203 | }
204 |
205 | func TestHandleDeploymentCreateNoEnvSet(t *testing.T) {
206 | log.SetOutput(ioutil.Discard)
207 | client = new(MockClient)
208 | req, err := http.NewRequest("GET", "/admin/k8s/deployment/create", nil)
209 | if err != nil {
210 | t.Fatal(err)
211 | }
212 |
213 | rr := httptest.NewRecorder()
214 | handler := http.HandlerFunc(handleAPI(handleDeploymentCreate))
215 | handler.ServeHTTP(rr, req)
216 |
217 | if status := rr.Code; status != http.StatusInternalServerError {
218 | t.Errorf("wrong status code: got %v want %v", status, http.StatusInternalServerError)
219 | }
220 |
221 | }
222 |
223 | func TestHandleNodes(t *testing.T) {
224 | log.SetOutput(ioutil.Discard)
225 | client = new(MockClient)
226 | req, err := http.NewRequest("GET", "/admin/k8s/nodes/get", nil)
227 | if err != nil {
228 | t.Fatal(err)
229 | }
230 |
231 | rr := httptest.NewRecorder()
232 | handler := http.HandlerFunc(handleAPI(handleNodes))
233 | handler.ServeHTTP(rr, req)
234 |
235 | if status := rr.Code; status != http.StatusOK {
236 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
237 | }
238 |
239 | var nodeList v1.NodeList
240 | if err := json.Unmarshal([]byte(rr.Body.String()), &nodeList); err != nil {
241 | t.Errorf("error turning response to podlist: %v", err)
242 | t.FailNow()
243 | }
244 |
245 | if len(nodeList.Items) < 2 {
246 | t.Errorf("nodelist.Items: got %d want %d", len(nodeList.Items), 2)
247 | }
248 |
249 | }
250 |
251 | func TestHandleNodeDrain(t *testing.T) {
252 | log.SetOutput(ioutil.Discard)
253 | client = new(MockClient)
254 | req, err := http.NewRequest("GET", "/admin/k8s/node/drain?node="+nodeName, nil)
255 | if err != nil {
256 | t.Fatal(err)
257 | }
258 |
259 | rr := httptest.NewRecorder()
260 | handler := http.HandlerFunc(handleAPI(handleNodeDrain))
261 | handler.ServeHTTP(rr, req)
262 |
263 | if status := rr.Code; status != http.StatusOK {
264 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
265 | }
266 |
267 | var node v1.Node
268 | if err := json.Unmarshal([]byte(rr.Body.String()), &node); err != nil {
269 | t.Errorf("error turning response to podlist: %v", err)
270 | t.FailNow()
271 | }
272 |
273 | if !node.Spec.Unschedulable {
274 | t.Errorf("node.Spec.Unschedulable: got %t want %t", node.Spec.Unschedulable, true)
275 | }
276 |
277 | }
278 |
279 | func TestHandleNodeUncordon(t *testing.T) {
280 | log.SetOutput(ioutil.Discard)
281 | client = new(MockClient)
282 | req, err := http.NewRequest("GET", "/admin/k8s/node/uncordon?node="+nodeName, nil)
283 | if err != nil {
284 | t.Fatal(err)
285 | }
286 |
287 | rr := httptest.NewRecorder()
288 | handler := http.HandlerFunc(handleAPI(handleNodeUncordon))
289 | handler.ServeHTTP(rr, req)
290 |
291 | if status := rr.Code; status != http.StatusOK {
292 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
293 | }
294 |
295 | var node v1.Node
296 | if err := json.Unmarshal([]byte(rr.Body.String()), &node); err != nil {
297 | t.Errorf("error turning response to podlist: %v", err)
298 | t.FailNow()
299 | }
300 |
301 | if node.Spec.Unschedulable {
302 | t.Errorf("node.Spec.Unschedulable: got %t want %t", node.Spec.Unschedulable, false)
303 | }
304 |
305 | }
306 |
--------------------------------------------------------------------------------
/apps/admin/containers/testdata/deployment_create.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "extensions/v1beta1",
3 | "kind": "Deployment",
4 | "metadata": {
5 | "annotations": {
6 | "deployment.kubernetes.io/revision": "1"
7 | },
8 | "creationTimestamp": "2017-10-19T06:09:47Z",
9 | "generation": 1,
10 | "labels": {
11 | "app": "api"
12 | },
13 | "name": "api-deployment",
14 | "namespace": "default",
15 | "resourceVersion": "38914618",
16 | "selfLink": "/apis/extensions/v1beta1/namespaces/default/deployments/api-deployment",
17 | "uid": "1bf2055d-b494-11e7-ab94-42010af0005b"
18 | },
19 | "spec": {
20 | "replicas": 1,
21 | "selector": {
22 | "matchLabels": {
23 | "app": "api"
24 | }
25 | },
26 | "strategy": {
27 | "rollingUpdate": {
28 | "maxSurge": 1,
29 | "maxUnavailable": 1
30 | },
31 | "type": "RollingUpdate"
32 | },
33 | "template": {
34 | "metadata": {
35 | "creationTimestamp": null,
36 | "labels": {
37 | "app": "api"
38 | }
39 | },
40 | "spec": {
41 | "containers": [
42 | {
43 | "image": "gcr.io/carnivaldemos/api",
44 | "imagePullPolicy": "Always",
45 | "name": "api-deployment",
46 | "ports": [
47 | {
48 | "containerPort": 8080,
49 | "protocol": "TCP"
50 | }
51 | ],
52 | "resources": {},
53 | "terminationMessagePath": "/dev/termination-log",
54 | "terminationMessagePolicy": "File"
55 | }
56 | ],
57 | "dnsPolicy": "ClusterFirst",
58 | "restartPolicy": "Always",
59 | "schedulerName": "default-scheduler",
60 | "securityContext": {},
61 | "terminationGracePeriodSeconds": 30
62 | }
63 | }
64 | },
65 | "status": {
66 | "availableReplicas": 12,
67 | "conditions": [
68 | {
69 | "lastTransitionTime": "2017-10-19T06:09:47Z",
70 | "lastUpdateTime": "2017-10-19T06:09:47Z",
71 | "message": "Deployment has minimum availability.",
72 | "reason": "MinimumReplicasAvailable",
73 | "status": "True",
74 | "type": "Available"
75 | }
76 | ],
77 | "observedGeneration": 12,
78 | "readyReplicas": 12,
79 | "replicas": 12,
80 | "updatedReplicas": 12
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/apps/admin/containers/testdata/deployment_delete.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "extensions/v1beta1",
3 | "kind": "Deployment",
4 | "metadata": {
5 | "annotations": {
6 | "deployment.kubernetes.io/revision": "1"
7 | },
8 | "creationTimestamp": "2017-10-19T06:09:47Z",
9 | "generation": 1,
10 | "labels": {
11 | "app": "api"
12 | },
13 | "name": "api-deployment",
14 | "namespace": "default",
15 | "resourceVersion": "38914618",
16 | "selfLink": "/apis/extensions/v1beta1/namespaces/default/deployments/api-deployment",
17 | "uid": "1bf2055d-b494-11e7-ab94-42010af0005b"
18 | },
19 | "spec": {
20 | "replicas": 1,
21 | "selector": {
22 | "matchLabels": {
23 | "app": "api"
24 | }
25 | },
26 | "strategy": {
27 | "rollingUpdate": {
28 | "maxSurge": 1,
29 | "maxUnavailable": 1
30 | },
31 | "type": "RollingUpdate"
32 | },
33 | "template": {
34 | "metadata": {
35 | "creationTimestamp": null,
36 | "labels": {
37 | "app": "api"
38 | }
39 | },
40 | "spec": {
41 | "containers": [
42 | {
43 | "image": "gcr.io/carnivaldemos/api",
44 | "imagePullPolicy": "Always",
45 | "name": "api-deployment",
46 | "ports": [
47 | {
48 | "containerPort": 8080,
49 | "protocol": "TCP"
50 | }
51 | ],
52 | "resources": {},
53 | "terminationMessagePath": "/dev/termination-log",
54 | "terminationMessagePolicy": "File"
55 | }
56 | ],
57 | "dnsPolicy": "ClusterFirst",
58 | "restartPolicy": "Always",
59 | "schedulerName": "default-scheduler",
60 | "securityContext": {},
61 | "terminationGracePeriodSeconds": 30
62 | }
63 | }
64 | },
65 | "status": {
66 | "availableReplicas": 12,
67 | "conditions": [
68 | {
69 | "lastTransitionTime": "2017-10-19T06:09:47Z",
70 | "lastUpdateTime": "2017-10-19T06:09:47Z",
71 | "message": "Deployment has minimum availability.",
72 | "reason": "MinimumReplicasAvailable",
73 | "status": "True",
74 | "type": "Available"
75 | }
76 | ],
77 | "observedGeneration": 12,
78 | "readyReplicas": 12,
79 | "replicas": 12,
80 | "updatedReplicas": 12
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/apps/admin/containers/testdata/empty.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/apps/admin/containers/testdata/node_schedulable.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "apiVersion": "v1",
4 | "kind": "Node",
5 | "metadata": {
6 | "annotations": {
7 | "node.alpha.kubernetes.io/ttl": "0",
8 | "volumes.kubernetes.io/controller-managed-attach-detach": "true"
9 | },
10 | "creationTimestamp": "2017-03-20T17:00:56Z",
11 | "labels": {
12 | "beta.kubernetes.io/arch": "amd64",
13 | "beta.kubernetes.io/instance-type": "n1-standard-4",
14 | "beta.kubernetes.io/os": "linux",
15 | "cloud.google.com/gke-nodepool": "default-pool",
16 | "failure-domain.beta.kubernetes.io/region": "us-central1",
17 | "failure-domain.beta.kubernetes.io/zone": "us-central1-c",
18 | "kubernetes.io/hostname": "gke-whack-a-pod-default-pool-8deaa3a5-b9p7"
19 | },
20 | "name": "gke-whack-a-pod-default-pool-8deaa3a5-b9p7",
21 | "namespace": "",
22 | "resourceVersion": "39104372",
23 | "selfLink": "/api/v1/nodesgke-whack-a-pod-default-pool-8deaa3a5-b9p7",
24 | "uid": "c8b956be-0d8e-11e7-b473-42010af000cf"
25 | },
26 | "spec": {
27 | "externalID": "6540334367623722115",
28 | "podCIDR": "10.120.0.0/24",
29 | "providerID": "gce://carnivaldemos/us-central1-c/gke-whack-a-pod-default-pool-8deaa3a5-b9p7"
30 | },
31 | "status": {
32 | "addresses": [
33 | {
34 | "address": "10.128.0.2",
35 | "type": "InternalIP"
36 | },
37 | {
38 | "address": "35.188.8.76",
39 | "type": "ExternalIP"
40 | },
41 | {
42 | "address": "gke-whack-a-pod-default-pool-8deaa3a5-b9p7",
43 | "type": "Hostname"
44 | }
45 | ],
46 | "allocatable": {
47 | "alpha.kubernetes.io/nvidia-gpu": "0",
48 | "cpu": "4",
49 | "memory": "15381708Ki",
50 | "pods": "110"
51 | },
52 | "capacity": {
53 | "alpha.kubernetes.io/nvidia-gpu": "0",
54 | "cpu": "4",
55 | "memory": "15381708Ki",
56 | "pods": "110"
57 | },
58 | "conditions": [
59 | {
60 | "lastHeartbeatTime": "2017-03-20T17:01:19Z",
61 | "lastTransitionTime": "2017-03-20T17:01:19Z",
62 | "message": "RouteController created a route",
63 | "reason": "RouteCreated",
64 | "status": "False",
65 | "type": "NetworkUnavailable"
66 | },
67 | {
68 | "lastHeartbeatTime": "2017-10-21T01:36:16Z",
69 | "lastTransitionTime": "2017-03-20T17:00:56Z",
70 | "message": "kubelet has sufficient disk space available",
71 | "reason": "KubeletHasSufficientDisk",
72 | "status": "False",
73 | "type": "OutOfDisk"
74 | },
75 | {
76 | "lastHeartbeatTime": "2017-10-21T01:36:16Z",
77 | "lastTransitionTime": "2017-03-20T17:00:56Z",
78 | "message": "kubelet has sufficient memory available",
79 | "reason": "KubeletHasSufficientMemory",
80 | "status": "False",
81 | "type": "MemoryPressure"
82 | },
83 | {
84 | "lastHeartbeatTime": "2017-10-21T01:36:16Z",
85 | "lastTransitionTime": "2017-03-20T17:00:56Z",
86 | "message": "kubelet has no disk pressure",
87 | "reason": "KubeletHasNoDiskPressure",
88 | "status": "False",
89 | "type": "DiskPressure"
90 | },
91 | {
92 | "lastHeartbeatTime": "2017-10-21T01:36:16Z",
93 | "lastTransitionTime": "2017-03-20T17:01:26Z",
94 | "message": "kubelet is posting ready status. AppArmor enabled",
95 | "reason": "KubeletReady",
96 | "status": "True",
97 | "type": "Ready"
98 | }
99 | ],
100 | "daemonEndpoints": {
101 | "kubeletEndpoint": {
102 | "Port": 10250
103 | }
104 | },
105 | "images": [
106 | {
107 | "names": [
108 | "\u003cnone\u003e@\u003cnone\u003e",
109 | "\u003cnone\u003e:\u003cnone\u003e"
110 | ],
111 | "sizeBytes": 471983629
112 | },
113 | {
114 | "names": [
115 | "\u003cnone\u003e@\u003cnone\u003e",
116 | "\u003cnone\u003e:\u003cnone\u003e"
117 | ],
118 | "sizeBytes": 471983503
119 | },
120 | {
121 | "names": [
122 | "\u003cnone\u003e@\u003cnone\u003e",
123 | "\u003cnone\u003e:\u003cnone\u003e"
124 | ],
125 | "sizeBytes": 471983302
126 | },
127 | {
128 | "names": [
129 | "\u003cnone\u003e@\u003cnone\u003e",
130 | "\u003cnone\u003e:\u003cnone\u003e"
131 | ],
132 | "sizeBytes": 471983302
133 | },
134 | {
135 | "names": [
136 | "\u003cnone\u003e@\u003cnone\u003e",
137 | "\u003cnone\u003e:\u003cnone\u003e"
138 | ],
139 | "sizeBytes": 471983290
140 | },
141 | {
142 | "names": [
143 | "\u003cnone\u003e@\u003cnone\u003e",
144 | "\u003cnone\u003e:\u003cnone\u003e"
145 | ],
146 | "sizeBytes": 471983275
147 | },
148 | {
149 | "names": [
150 | "\u003cnone\u003e@\u003cnone\u003e",
151 | "\u003cnone\u003e:\u003cnone\u003e"
152 | ],
153 | "sizeBytes": 471983194
154 | },
155 | {
156 | "names": [
157 | "\u003cnone\u003e@\u003cnone\u003e",
158 | "\u003cnone\u003e:\u003cnone\u003e"
159 | ],
160 | "sizeBytes": 471979999
161 | },
162 | {
163 | "names": [
164 | "\u003cnone\u003e@\u003cnone\u003e",
165 | "\u003cnone\u003e:\u003cnone\u003e"
166 | ],
167 | "sizeBytes": 471979987
168 | },
169 | {
170 | "names": [
171 | "\u003cnone\u003e@\u003cnone\u003e",
172 | "\u003cnone\u003e:\u003cnone\u003e"
173 | ],
174 | "sizeBytes": 471978343
175 | },
176 | {
177 | "names": [
178 | "\u003cnone\u003e@\u003cnone\u003e",
179 | "\u003cnone\u003e:\u003cnone\u003e"
180 | ],
181 | "sizeBytes": 471978337
182 | },
183 | {
184 | "names": [
185 | "\u003cnone\u003e@\u003cnone\u003e",
186 | "\u003cnone\u003e:\u003cnone\u003e"
187 | ],
188 | "sizeBytes": 471978322
189 | },
190 | {
191 | "names": [
192 | "\u003cnone\u003e@\u003cnone\u003e",
193 | "\u003cnone\u003e:\u003cnone\u003e"
194 | ],
195 | "sizeBytes": 471978229
196 | },
197 | {
198 | "names": [
199 | "\u003cnone\u003e@\u003cnone\u003e",
200 | "\u003cnone\u003e:\u003cnone\u003e"
201 | ],
202 | "sizeBytes": 471978172
203 | },
204 | {
205 | "names": [
206 | "\u003cnone\u003e@\u003cnone\u003e",
207 | "\u003cnone\u003e:\u003cnone\u003e"
208 | ],
209 | "sizeBytes": 471978151
210 | },
211 | {
212 | "names": [
213 | "\u003cnone\u003e@\u003cnone\u003e",
214 | "\u003cnone\u003e:\u003cnone\u003e"
215 | ],
216 | "sizeBytes": 471978046
217 | },
218 | {
219 | "names": [
220 | "\u003cnone\u003e@\u003cnone\u003e",
221 | "\u003cnone\u003e:\u003cnone\u003e"
222 | ],
223 | "sizeBytes": 471977977
224 | },
225 | {
226 | "names": [
227 | "\u003cnone\u003e@\u003cnone\u003e",
228 | "\u003cnone\u003e:\u003cnone\u003e"
229 | ],
230 | "sizeBytes": 471977950
231 | },
232 | {
233 | "names": [
234 | "\u003cnone\u003e@\u003cnone\u003e",
235 | "\u003cnone\u003e:\u003cnone\u003e"
236 | ],
237 | "sizeBytes": 471977950
238 | },
239 | {
240 | "names": [
241 | "\u003cnone\u003e@\u003cnone\u003e",
242 | "\u003cnone\u003e:\u003cnone\u003e"
243 | ],
244 | "sizeBytes": 471977944
245 | },
246 | {
247 | "names": [
248 | "\u003cnone\u003e@\u003cnone\u003e",
249 | "\u003cnone\u003e:\u003cnone\u003e"
250 | ],
251 | "sizeBytes": 471977944
252 | },
253 | {
254 | "names": [
255 | "\u003cnone\u003e@\u003cnone\u003e",
256 | "\u003cnone\u003e:\u003cnone\u003e"
257 | ],
258 | "sizeBytes": 471977884
259 | },
260 | {
261 | "names": [
262 | "\u003cnone\u003e@\u003cnone\u003e",
263 | "\u003cnone\u003e:\u003cnone\u003e"
264 | ],
265 | "sizeBytes": 471977881
266 | },
267 | {
268 | "names": [
269 | "\u003cnone\u003e@\u003cnone\u003e",
270 | "\u003cnone\u003e:\u003cnone\u003e"
271 | ],
272 | "sizeBytes": 471977863
273 | },
274 | {
275 | "names": [
276 | "\u003cnone\u003e@\u003cnone\u003e",
277 | "\u003cnone\u003e:\u003cnone\u003e"
278 | ],
279 | "sizeBytes": 471977863
280 | },
281 | {
282 | "names": [
283 | "\u003cnone\u003e@\u003cnone\u003e",
284 | "\u003cnone\u003e:\u003cnone\u003e"
285 | ],
286 | "sizeBytes": 471977860
287 | },
288 | {
289 | "names": [
290 | "\u003cnone\u003e@\u003cnone\u003e",
291 | "\u003cnone\u003e:\u003cnone\u003e"
292 | ],
293 | "sizeBytes": 471977848
294 | },
295 | {
296 | "names": [
297 | "\u003cnone\u003e@\u003cnone\u003e",
298 | "\u003cnone\u003e:\u003cnone\u003e"
299 | ],
300 | "sizeBytes": 471977305
301 | },
302 | {
303 | "names": [
304 | "\u003cnone\u003e@\u003cnone\u003e",
305 | "\u003cnone\u003e:\u003cnone\u003e"
306 | ],
307 | "sizeBytes": 471977278
308 | },
309 | {
310 | "names": [
311 | "\u003cnone\u003e@\u003cnone\u003e",
312 | "\u003cnone\u003e:\u003cnone\u003e"
313 | ],
314 | "sizeBytes": 471977269
315 | },
316 | {
317 | "names": [
318 | "\u003cnone\u003e@\u003cnone\u003e",
319 | "\u003cnone\u003e:\u003cnone\u003e"
320 | ],
321 | "sizeBytes": 471977251
322 | },
323 | {
324 | "names": [
325 | "\u003cnone\u003e@\u003cnone\u003e",
326 | "\u003cnone\u003e:\u003cnone\u003e"
327 | ],
328 | "sizeBytes": 471977248
329 | },
330 | {
331 | "names": [
332 | "\u003cnone\u003e@\u003cnone\u003e",
333 | "\u003cnone\u003e:\u003cnone\u003e"
334 | ],
335 | "sizeBytes": 471977248
336 | },
337 | {
338 | "names": [
339 | "\u003cnone\u003e@\u003cnone\u003e",
340 | "\u003cnone\u003e:\u003cnone\u003e"
341 | ],
342 | "sizeBytes": 471977245
343 | },
344 | {
345 | "names": [
346 | "\u003cnone\u003e@\u003cnone\u003e",
347 | "\u003cnone\u003e:\u003cnone\u003e"
348 | ],
349 | "sizeBytes": 471977239
350 | },
351 | {
352 | "names": [
353 | "\u003cnone\u003e@\u003cnone\u003e",
354 | "\u003cnone\u003e:\u003cnone\u003e"
355 | ],
356 | "sizeBytes": 471976675
357 | },
358 | {
359 | "names": [
360 | "\u003cnone\u003e@\u003cnone\u003e",
361 | "\u003cnone\u003e:\u003cnone\u003e"
362 | ],
363 | "sizeBytes": 471976234
364 | },
365 | {
366 | "names": [
367 | "\u003cnone\u003e@\u003cnone\u003e",
368 | "\u003cnone\u003e:\u003cnone\u003e"
369 | ],
370 | "sizeBytes": 471976234
371 | },
372 | {
373 | "names": [
374 | "\u003cnone\u003e@\u003cnone\u003e",
375 | "\u003cnone\u003e:\u003cnone\u003e"
376 | ],
377 | "sizeBytes": 471976126
378 | },
379 | {
380 | "names": [
381 | "\u003cnone\u003e@\u003cnone\u003e",
382 | "\u003cnone\u003e:\u003cnone\u003e"
383 | ],
384 | "sizeBytes": 471974611
385 | },
386 | {
387 | "names": [
388 | "\u003cnone\u003e@\u003cnone\u003e",
389 | "\u003cnone\u003e:\u003cnone\u003e"
390 | ],
391 | "sizeBytes": 471974608
392 | },
393 | {
394 | "names": [
395 | "\u003cnone\u003e@\u003cnone\u003e",
396 | "\u003cnone\u003e:\u003cnone\u003e"
397 | ],
398 | "sizeBytes": 471974605
399 | },
400 | {
401 | "names": [
402 | "\u003cnone\u003e@\u003cnone\u003e",
403 | "\u003cnone\u003e:\u003cnone\u003e"
404 | ],
405 | "sizeBytes": 471974398
406 | },
407 | {
408 | "names": [
409 | "\u003cnone\u003e@\u003cnone\u003e",
410 | "\u003cnone\u003e:\u003cnone\u003e"
411 | ],
412 | "sizeBytes": 471973969
413 | },
414 | {
415 | "names": [
416 | "\u003cnone\u003e@\u003cnone\u003e",
417 | "\u003cnone\u003e:\u003cnone\u003e"
418 | ],
419 | "sizeBytes": 471973726
420 | },
421 | {
422 | "names": [
423 | "\u003cnone\u003e@\u003cnone\u003e",
424 | "\u003cnone\u003e:\u003cnone\u003e"
425 | ],
426 | "sizeBytes": 471970832
427 | },
428 | {
429 | "names": [
430 | "\u003cnone\u003e@\u003cnone\u003e",
431 | "\u003cnone\u003e:\u003cnone\u003e"
432 | ],
433 | "sizeBytes": 471970820
434 | },
435 | {
436 | "names": [
437 | "\u003cnone\u003e@\u003cnone\u003e",
438 | "\u003cnone\u003e:\u003cnone\u003e"
439 | ],
440 | "sizeBytes": 471970812
441 | },
442 | {
443 | "names": [
444 | "\u003cnone\u003e@\u003cnone\u003e",
445 | "\u003cnone\u003e:\u003cnone\u003e"
446 | ],
447 | "sizeBytes": 471970810
448 | },
449 | {
450 | "names": [
451 | "\u003cnone\u003e@\u003cnone\u003e",
452 | "\u003cnone\u003e:\u003cnone\u003e"
453 | ],
454 | "sizeBytes": 471970768
455 | }
456 | ],
457 | "nodeInfo": {
458 | "architecture": "amd64",
459 | "bootID": "020a803c-76eb-4de6-ad2b-4b52af3f529d",
460 | "containerRuntimeVersion": "docker://1.11.2",
461 | "kernelVersion": "4.4.21+",
462 | "kubeProxyVersion": "v1.5.3",
463 | "kubeletVersion": "v1.5.3",
464 | "machineID": "9ae15998aaa220842be1486ea9751460",
465 | "operatingSystem": "linux",
466 | "osImage": "Container-Optimized OS from Google",
467 | "systemUUID": "9AE15998-AAA2-2084-2BE1-486EA9751460"
468 | }
469 | }
470 | }
471 |
--------------------------------------------------------------------------------
/apps/admin/containers/testdata/node_unschedulable.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "apiVersion": "v1",
4 | "kind": "Node",
5 | "metadata": {
6 | "annotations": {
7 | "node.alpha.kubernetes.io/ttl": "0",
8 | "volumes.kubernetes.io/controller-managed-attach-detach": "true"
9 | },
10 | "creationTimestamp": "2017-03-20T17:00:56Z",
11 | "labels": {
12 | "beta.kubernetes.io/arch": "amd64",
13 | "beta.kubernetes.io/instance-type": "n1-standard-4",
14 | "beta.kubernetes.io/os": "linux",
15 | "cloud.google.com/gke-nodepool": "default-pool",
16 | "failure-domain.beta.kubernetes.io/region": "us-central1",
17 | "failure-domain.beta.kubernetes.io/zone": "us-central1-c",
18 | "kubernetes.io/hostname": "gke-whack-a-pod-default-pool-8deaa3a5-b9p7"
19 | },
20 | "name": "gke-whack-a-pod-default-pool-8deaa3a5-b9p7",
21 | "namespace": "",
22 | "resourceVersion": "39104372",
23 | "selfLink": "/api/v1/nodesgke-whack-a-pod-default-pool-8deaa3a5-b9p7",
24 | "uid": "c8b956be-0d8e-11e7-b473-42010af000cf"
25 | },
26 | "spec": {
27 | "externalID": "6540334367623722115",
28 | "podCIDR": "10.120.0.0/24",
29 | "providerID": "gce://carnivaldemos/us-central1-c/gke-whack-a-pod-default-pool-8deaa3a5-b9p7",
30 | "unschedulable": true
31 | },
32 | "status": {
33 | "addresses": [
34 | {
35 | "address": "10.128.0.2",
36 | "type": "InternalIP"
37 | },
38 | {
39 | "address": "35.188.8.76",
40 | "type": "ExternalIP"
41 | },
42 | {
43 | "address": "gke-whack-a-pod-default-pool-8deaa3a5-b9p7",
44 | "type": "Hostname"
45 | }
46 | ],
47 | "allocatable": {
48 | "alpha.kubernetes.io/nvidia-gpu": "0",
49 | "cpu": "4",
50 | "memory": "15381708Ki",
51 | "pods": "110"
52 | },
53 | "capacity": {
54 | "alpha.kubernetes.io/nvidia-gpu": "0",
55 | "cpu": "4",
56 | "memory": "15381708Ki",
57 | "pods": "110"
58 | },
59 | "conditions": [
60 | {
61 | "lastHeartbeatTime": "2017-03-20T17:01:19Z",
62 | "lastTransitionTime": "2017-03-20T17:01:19Z",
63 | "message": "RouteController created a route",
64 | "reason": "RouteCreated",
65 | "status": "False",
66 | "type": "NetworkUnavailable"
67 | },
68 | {
69 | "lastHeartbeatTime": "2017-10-21T01:36:16Z",
70 | "lastTransitionTime": "2017-03-20T17:00:56Z",
71 | "message": "kubelet has sufficient disk space available",
72 | "reason": "KubeletHasSufficientDisk",
73 | "status": "False",
74 | "type": "OutOfDisk"
75 | },
76 | {
77 | "lastHeartbeatTime": "2017-10-21T01:36:16Z",
78 | "lastTransitionTime": "2017-03-20T17:00:56Z",
79 | "message": "kubelet has sufficient memory available",
80 | "reason": "KubeletHasSufficientMemory",
81 | "status": "False",
82 | "type": "MemoryPressure"
83 | },
84 | {
85 | "lastHeartbeatTime": "2017-10-21T01:36:16Z",
86 | "lastTransitionTime": "2017-03-20T17:00:56Z",
87 | "message": "kubelet has no disk pressure",
88 | "reason": "KubeletHasNoDiskPressure",
89 | "status": "False",
90 | "type": "DiskPressure"
91 | },
92 | {
93 | "lastHeartbeatTime": "2017-10-21T01:36:16Z",
94 | "lastTransitionTime": "2017-03-20T17:01:26Z",
95 | "message": "kubelet is posting ready status. AppArmor enabled",
96 | "reason": "KubeletReady",
97 | "status": "True",
98 | "type": "Ready"
99 | }
100 | ],
101 | "daemonEndpoints": {
102 | "kubeletEndpoint": {
103 | "Port": 10250
104 | }
105 | },
106 | "images": [
107 | {
108 | "names": [
109 | "\u003cnone\u003e@\u003cnone\u003e",
110 | "\u003cnone\u003e:\u003cnone\u003e"
111 | ],
112 | "sizeBytes": 471983629
113 | },
114 | {
115 | "names": [
116 | "\u003cnone\u003e@\u003cnone\u003e",
117 | "\u003cnone\u003e:\u003cnone\u003e"
118 | ],
119 | "sizeBytes": 471983503
120 | },
121 | {
122 | "names": [
123 | "\u003cnone\u003e@\u003cnone\u003e",
124 | "\u003cnone\u003e:\u003cnone\u003e"
125 | ],
126 | "sizeBytes": 471983302
127 | },
128 | {
129 | "names": [
130 | "\u003cnone\u003e@\u003cnone\u003e",
131 | "\u003cnone\u003e:\u003cnone\u003e"
132 | ],
133 | "sizeBytes": 471983302
134 | },
135 | {
136 | "names": [
137 | "\u003cnone\u003e@\u003cnone\u003e",
138 | "\u003cnone\u003e:\u003cnone\u003e"
139 | ],
140 | "sizeBytes": 471983290
141 | },
142 | {
143 | "names": [
144 | "\u003cnone\u003e@\u003cnone\u003e",
145 | "\u003cnone\u003e:\u003cnone\u003e"
146 | ],
147 | "sizeBytes": 471983275
148 | },
149 | {
150 | "names": [
151 | "\u003cnone\u003e@\u003cnone\u003e",
152 | "\u003cnone\u003e:\u003cnone\u003e"
153 | ],
154 | "sizeBytes": 471983194
155 | },
156 | {
157 | "names": [
158 | "\u003cnone\u003e@\u003cnone\u003e",
159 | "\u003cnone\u003e:\u003cnone\u003e"
160 | ],
161 | "sizeBytes": 471979999
162 | },
163 | {
164 | "names": [
165 | "\u003cnone\u003e@\u003cnone\u003e",
166 | "\u003cnone\u003e:\u003cnone\u003e"
167 | ],
168 | "sizeBytes": 471979987
169 | },
170 | {
171 | "names": [
172 | "\u003cnone\u003e@\u003cnone\u003e",
173 | "\u003cnone\u003e:\u003cnone\u003e"
174 | ],
175 | "sizeBytes": 471978343
176 | },
177 | {
178 | "names": [
179 | "\u003cnone\u003e@\u003cnone\u003e",
180 | "\u003cnone\u003e:\u003cnone\u003e"
181 | ],
182 | "sizeBytes": 471978337
183 | },
184 | {
185 | "names": [
186 | "\u003cnone\u003e@\u003cnone\u003e",
187 | "\u003cnone\u003e:\u003cnone\u003e"
188 | ],
189 | "sizeBytes": 471978322
190 | },
191 | {
192 | "names": [
193 | "\u003cnone\u003e@\u003cnone\u003e",
194 | "\u003cnone\u003e:\u003cnone\u003e"
195 | ],
196 | "sizeBytes": 471978229
197 | },
198 | {
199 | "names": [
200 | "\u003cnone\u003e@\u003cnone\u003e",
201 | "\u003cnone\u003e:\u003cnone\u003e"
202 | ],
203 | "sizeBytes": 471978172
204 | },
205 | {
206 | "names": [
207 | "\u003cnone\u003e@\u003cnone\u003e",
208 | "\u003cnone\u003e:\u003cnone\u003e"
209 | ],
210 | "sizeBytes": 471978151
211 | },
212 | {
213 | "names": [
214 | "\u003cnone\u003e@\u003cnone\u003e",
215 | "\u003cnone\u003e:\u003cnone\u003e"
216 | ],
217 | "sizeBytes": 471978046
218 | },
219 | {
220 | "names": [
221 | "\u003cnone\u003e@\u003cnone\u003e",
222 | "\u003cnone\u003e:\u003cnone\u003e"
223 | ],
224 | "sizeBytes": 471977977
225 | },
226 | {
227 | "names": [
228 | "\u003cnone\u003e@\u003cnone\u003e",
229 | "\u003cnone\u003e:\u003cnone\u003e"
230 | ],
231 | "sizeBytes": 471977950
232 | },
233 | {
234 | "names": [
235 | "\u003cnone\u003e@\u003cnone\u003e",
236 | "\u003cnone\u003e:\u003cnone\u003e"
237 | ],
238 | "sizeBytes": 471977950
239 | },
240 | {
241 | "names": [
242 | "\u003cnone\u003e@\u003cnone\u003e",
243 | "\u003cnone\u003e:\u003cnone\u003e"
244 | ],
245 | "sizeBytes": 471977944
246 | },
247 | {
248 | "names": [
249 | "\u003cnone\u003e@\u003cnone\u003e",
250 | "\u003cnone\u003e:\u003cnone\u003e"
251 | ],
252 | "sizeBytes": 471977944
253 | },
254 | {
255 | "names": [
256 | "\u003cnone\u003e@\u003cnone\u003e",
257 | "\u003cnone\u003e:\u003cnone\u003e"
258 | ],
259 | "sizeBytes": 471977884
260 | },
261 | {
262 | "names": [
263 | "\u003cnone\u003e@\u003cnone\u003e",
264 | "\u003cnone\u003e:\u003cnone\u003e"
265 | ],
266 | "sizeBytes": 471977881
267 | },
268 | {
269 | "names": [
270 | "\u003cnone\u003e@\u003cnone\u003e",
271 | "\u003cnone\u003e:\u003cnone\u003e"
272 | ],
273 | "sizeBytes": 471977863
274 | },
275 | {
276 | "names": [
277 | "\u003cnone\u003e@\u003cnone\u003e",
278 | "\u003cnone\u003e:\u003cnone\u003e"
279 | ],
280 | "sizeBytes": 471977863
281 | },
282 | {
283 | "names": [
284 | "\u003cnone\u003e@\u003cnone\u003e",
285 | "\u003cnone\u003e:\u003cnone\u003e"
286 | ],
287 | "sizeBytes": 471977860
288 | },
289 | {
290 | "names": [
291 | "\u003cnone\u003e@\u003cnone\u003e",
292 | "\u003cnone\u003e:\u003cnone\u003e"
293 | ],
294 | "sizeBytes": 471977848
295 | },
296 | {
297 | "names": [
298 | "\u003cnone\u003e@\u003cnone\u003e",
299 | "\u003cnone\u003e:\u003cnone\u003e"
300 | ],
301 | "sizeBytes": 471977305
302 | },
303 | {
304 | "names": [
305 | "\u003cnone\u003e@\u003cnone\u003e",
306 | "\u003cnone\u003e:\u003cnone\u003e"
307 | ],
308 | "sizeBytes": 471977278
309 | },
310 | {
311 | "names": [
312 | "\u003cnone\u003e@\u003cnone\u003e",
313 | "\u003cnone\u003e:\u003cnone\u003e"
314 | ],
315 | "sizeBytes": 471977269
316 | },
317 | {
318 | "names": [
319 | "\u003cnone\u003e@\u003cnone\u003e",
320 | "\u003cnone\u003e:\u003cnone\u003e"
321 | ],
322 | "sizeBytes": 471977251
323 | },
324 | {
325 | "names": [
326 | "\u003cnone\u003e@\u003cnone\u003e",
327 | "\u003cnone\u003e:\u003cnone\u003e"
328 | ],
329 | "sizeBytes": 471977248
330 | },
331 | {
332 | "names": [
333 | "\u003cnone\u003e@\u003cnone\u003e",
334 | "\u003cnone\u003e:\u003cnone\u003e"
335 | ],
336 | "sizeBytes": 471977248
337 | },
338 | {
339 | "names": [
340 | "\u003cnone\u003e@\u003cnone\u003e",
341 | "\u003cnone\u003e:\u003cnone\u003e"
342 | ],
343 | "sizeBytes": 471977245
344 | },
345 | {
346 | "names": [
347 | "\u003cnone\u003e@\u003cnone\u003e",
348 | "\u003cnone\u003e:\u003cnone\u003e"
349 | ],
350 | "sizeBytes": 471977239
351 | },
352 | {
353 | "names": [
354 | "\u003cnone\u003e@\u003cnone\u003e",
355 | "\u003cnone\u003e:\u003cnone\u003e"
356 | ],
357 | "sizeBytes": 471976675
358 | },
359 | {
360 | "names": [
361 | "\u003cnone\u003e@\u003cnone\u003e",
362 | "\u003cnone\u003e:\u003cnone\u003e"
363 | ],
364 | "sizeBytes": 471976234
365 | },
366 | {
367 | "names": [
368 | "\u003cnone\u003e@\u003cnone\u003e",
369 | "\u003cnone\u003e:\u003cnone\u003e"
370 | ],
371 | "sizeBytes": 471976234
372 | },
373 | {
374 | "names": [
375 | "\u003cnone\u003e@\u003cnone\u003e",
376 | "\u003cnone\u003e:\u003cnone\u003e"
377 | ],
378 | "sizeBytes": 471976126
379 | },
380 | {
381 | "names": [
382 | "\u003cnone\u003e@\u003cnone\u003e",
383 | "\u003cnone\u003e:\u003cnone\u003e"
384 | ],
385 | "sizeBytes": 471974611
386 | },
387 | {
388 | "names": [
389 | "\u003cnone\u003e@\u003cnone\u003e",
390 | "\u003cnone\u003e:\u003cnone\u003e"
391 | ],
392 | "sizeBytes": 471974608
393 | },
394 | {
395 | "names": [
396 | "\u003cnone\u003e@\u003cnone\u003e",
397 | "\u003cnone\u003e:\u003cnone\u003e"
398 | ],
399 | "sizeBytes": 471974605
400 | },
401 | {
402 | "names": [
403 | "\u003cnone\u003e@\u003cnone\u003e",
404 | "\u003cnone\u003e:\u003cnone\u003e"
405 | ],
406 | "sizeBytes": 471974398
407 | },
408 | {
409 | "names": [
410 | "\u003cnone\u003e@\u003cnone\u003e",
411 | "\u003cnone\u003e:\u003cnone\u003e"
412 | ],
413 | "sizeBytes": 471973969
414 | },
415 | {
416 | "names": [
417 | "\u003cnone\u003e@\u003cnone\u003e",
418 | "\u003cnone\u003e:\u003cnone\u003e"
419 | ],
420 | "sizeBytes": 471973726
421 | },
422 | {
423 | "names": [
424 | "\u003cnone\u003e@\u003cnone\u003e",
425 | "\u003cnone\u003e:\u003cnone\u003e"
426 | ],
427 | "sizeBytes": 471970832
428 | },
429 | {
430 | "names": [
431 | "\u003cnone\u003e@\u003cnone\u003e",
432 | "\u003cnone\u003e:\u003cnone\u003e"
433 | ],
434 | "sizeBytes": 471970820
435 | },
436 | {
437 | "names": [
438 | "\u003cnone\u003e@\u003cnone\u003e",
439 | "\u003cnone\u003e:\u003cnone\u003e"
440 | ],
441 | "sizeBytes": 471970812
442 | },
443 | {
444 | "names": [
445 | "\u003cnone\u003e@\u003cnone\u003e",
446 | "\u003cnone\u003e:\u003cnone\u003e"
447 | ],
448 | "sizeBytes": 471970810
449 | },
450 | {
451 | "names": [
452 | "\u003cnone\u003e@\u003cnone\u003e",
453 | "\u003cnone\u003e:\u003cnone\u003e"
454 | ],
455 | "sizeBytes": 471970768
456 | }
457 | ],
458 | "nodeInfo": {
459 | "architecture": "amd64",
460 | "bootID": "020a803c-76eb-4de6-ad2b-4b52af3f529d",
461 | "containerRuntimeVersion": "docker://1.11.2",
462 | "kernelVersion": "4.4.21+",
463 | "kubeProxyVersion": "v1.5.3",
464 | "kubeletVersion": "v1.5.3",
465 | "machineID": "9ae15998aaa220842be1486ea9751460",
466 | "operatingSystem": "linux",
467 | "osImage": "Container-Optimized OS from Google",
468 | "systemUUID": "9AE15998-AAA2-2084-2BE1-486EA9751460"
469 | }
470 | }
471 | }
472 |
--------------------------------------------------------------------------------
/apps/admin/containers/testdata/pod.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiVersion": "v1",
3 | "kind": "Pod",
4 | "metadata": {
5 | "generateName": "api-deployment-1435701907-",
6 | "labels": {
7 | "app": "api"
8 | },
9 | "name": "api-deployment-1435701907-xx9lm",
10 | "namespace": "default",
11 | "ownerReferences": [
12 | {
13 | "apiVersion": "extensions/v1beta1",
14 | "controller": true,
15 | "kind": "ReplicaSet",
16 | "name": "api-deployment-1435701907"
17 | }
18 | ],
19 | "selfLink": "/api/v1/namespaces/default/pods/api-deployment-1435701907-xx9lm"
20 | },
21 | "spec": {
22 | "containers": [
23 | {
24 | "image": "gcr.io/carnivaldemos/api",
25 | "imagePullPolicy": "Always",
26 | "name": "api-deployment",
27 | "ports": [
28 | {
29 | "containerPort": 8080,
30 | "protocol": "TCP"
31 | }
32 | ]
33 | }
34 | ],
35 | "dnsPolicy": "ClusterFirst",
36 | "nodeName": "gke-whack-a-pod-default-pool-8deaa3a5-znz4",
37 | "restartPolicy": "Always",
38 | "schedulerName": "default-scheduler",
39 | "terminationGracePeriodSeconds": 30,
40 | "tolerations": [
41 | {
42 | "effect": "NoExecute",
43 | "key": "node.alpha.kubernetes.io/notReady",
44 | "operator": "Exists",
45 | "tolerationSeconds": 300
46 | },
47 | {
48 | "effect": "NoExecute",
49 | "key": "node.alpha.kubernetes.io/unreachable",
50 | "operator": "Exists",
51 | "tolerationSeconds": 300
52 | }
53 | ],
54 | "volumes": [
55 | {
56 | "name": "default-token-4q4nh",
57 | "secret": {
58 | "defaultMode": 420,
59 | "secretName": "default-token-4q4nh"
60 | }
61 | }
62 | ]
63 | },
64 | "status": {
65 | "hostIP": "10.128.0.3",
66 | "phase": "Running",
67 | "podIP": "10.120.1.246"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/apps/admin/docker/Makefile:
--------------------------------------------------------------------------------
1 | BASEDIR = $(shell pwd)
2 |
3 | .DEFAULT_GOAL := app
4 |
5 | app: clean build serve
6 |
7 |
8 | main:
9 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "$(BASEDIR)/../containers/main" "$(BASEDIR)/../containers/main.go" "$(BASEDIR)/../containers/kubernetes.go"
10 |
11 | build: main
12 | docker build -t admin "$(BASEDIR)/../containers/."
13 |
14 | serve:
15 | docker run --name=admin -d -P -p 8080:8080 admin
16 |
17 | clean:
18 | -docker stop admin
19 | -docker rm admin
20 | -docker rmi admin
--------------------------------------------------------------------------------
/apps/admin/kubernetes/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | BASEDIR = $(shell pwd)
15 |
16 | include ../../../Makefile.properties
17 |
18 |
19 | reset: clean.deployment app
20 | say "App Reset"
21 |
22 | reset.safe:
23 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "$(BASEDIR)/../containers/main" "$(BASEDIR)/../containers/main.go" "$(BASEDIR)/../containers/kubernetes.go"
24 | gcloud container builds submit "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO)/admin
25 | -kubectl delete deployment admin-deployment
26 | -kubectl run admin-deployment --image=$(DOCKERREPO)/admin --replicas=1 --port=8080 --labels=app=admin --env="APIIMAGE=$(DOCKERREPO)/api"
27 | say "app refresh complete"
28 |
29 | app: build deploy
30 |
31 | main:
32 | go get github.com/gorilla/mux
33 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "$(BASEDIR)/../containers/main" "$(BASEDIR)/../containers/main.go" "$(BASEDIR)/../containers/kubernetes.go"
34 |
35 | build: env main
36 | gcloud container builds submit "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO)/admin
37 |
38 | build.dockerhub: main
39 | docker build "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO):admin
40 | docker push $(DOCKERREPO):admin
41 |
42 | build.generic: main
43 | docker build "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO)/whackapod-admin
44 | docker push $(DOCKERREPO)/whackapod-admin
45 |
46 | deploy: env creds deployment service
47 |
48 | deploy.minikube: deployment service
49 |
50 | deploy.minikube.dockerhub:
51 | kubectl run admin-deployment --image=$(DOCKERREPO):admin --replicas=1 --port=8080 --labels=app=admin --env="APIIMAGE=$(DOCKERREPO):api"
52 | kubectl expose deployment admin-deployment --name=admin --target-port=8080 --type=NodePort --labels="app=admin"
53 | kubectl create serviceaccount wap-admin
54 | kubectl create clusterrolebinding wap-admin --clusterrole=cluster-admin --serviceaccount=default:wap-admin
55 | kubectl set serviceaccount deployment admin-deployment wap-admin
56 |
57 | deploy.generic:
58 | kubectl run admin-deployment --image=$(DOCKERREPO)/whackapod-admin --replicas=1 --port=8080 --labels=app=admin --env="APIIMAGE=$(DOCKERREPO)/whackapod-api"
59 | kubectl expose deployment admin-deployment --name=admin --target-port=8080 --type=NodePort --labels="app=admin"
60 | kubectl create serviceaccount wap-admin
61 | kubectl create clusterrolebinding wap-admin --clusterrole=cluster-admin --serviceaccount=$$(kubectl config view -o jsonpath="{.contexts[?(@.name==\"$$(kubectl config current-context)\")].context.namespace}"):wap-admin
62 | kubectl set serviceaccount deployment admin-deployment wap-admin
63 |
64 | test:
65 | cd "$(BASEDIR)/../containers" && go test
66 |
67 | deployment:
68 | kubectl run admin-deployment --image=$(DOCKERREPO)/admin --replicas=1 --port=8080 --labels=app=admin --env="APIIMAGE=$(DOCKERREPO)/api"
69 |
70 | service:
71 | kubectl expose deployment admin-deployment --name=admin --target-port=8080 --type=NodePort --labels="app=admin"
72 |
73 | clean: env creds clean.deployment clean.service
74 |
75 | clean.minikube: clean.deployment clean.service
76 |
77 | clean.minikube.dockerhub: clean.deployment clean.service
78 | -kubectl delete serviceaccount wap-admin
79 | -kubectl delete clusterrolebinding wap-admin
80 |
81 | clean.deployment:
82 | -kubectl delete deployment admin-deployment
83 |
84 | clean.service:
85 | -kubectl delete service admin
86 |
87 | clean.generic: clean.deployment clean.service
88 | -kubectl delete serviceaccount wap-admin
89 | -kubectl delete clusterrolebinding wap-admin
90 |
91 | retry: clean build deploy
92 |
93 | config: env
94 | echo "No custom config needed for admin"
95 |
--------------------------------------------------------------------------------
/apps/api/containers/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | FROM scratch
15 | ADD main /
16 | CMD ["/main"]
--------------------------------------------------------------------------------
/apps/api/containers/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "math/rand"
7 | "net/http"
8 | "os"
9 | "strconv"
10 | "time"
11 | )
12 |
13 | var srv = http.Server{
14 | ReadTimeout: 5 * time.Second,
15 | WriteTimeout: 10 * time.Second,
16 | Addr: ":8080",
17 | Handler: handler(),
18 | }
19 |
20 | func main() {
21 | log.Printf("starting whack a pod color api")
22 | srv.ListenAndServe()
23 | }
24 |
25 | func handler() http.Handler {
26 | r := http.NewServeMux()
27 | r.HandleFunc("/", health)
28 | r.HandleFunc("/healthz", health)
29 | r.HandleFunc("/api/healthz", health)
30 | r.HandleFunc("/api/color", color)
31 | r.HandleFunc("/api/color/", color)
32 | r.HandleFunc("/api/color-complete", colorComplete)
33 | r.HandleFunc("/api/color-complete/", colorComplete)
34 | r.HandleFunc("/color", color)
35 | r.HandleFunc("/color/", color)
36 | r.HandleFunc("/color-complete", colorComplete)
37 | r.HandleFunc("/color-complete/", colorComplete)
38 |
39 | return r
40 | }
41 |
42 | func health(w http.ResponseWriter, r *http.Request) {
43 | w.Header().Add("Access-Control-Allow-Origin", "*")
44 | w.WriteHeader(http.StatusOK)
45 | fmt.Fprint(w, "ok")
46 | }
47 |
48 | func color(w http.ResponseWriter, r *http.Request) {
49 | w.Header().Add("Access-Control-Allow-Origin", "*")
50 | w.WriteHeader(http.StatusOK)
51 | fmt.Fprint(w, hexColorString())
52 | }
53 |
54 | func colorComplete(w http.ResponseWriter, r *http.Request) {
55 | status := http.StatusOK
56 | msg := ""
57 |
58 | h, err := os.Hostname()
59 | if err != nil {
60 | msg = fmt.Sprintf("{\"error\":\"could retrieve hostname: %v\"}", err)
61 | status = http.StatusInternalServerError
62 | } else {
63 | msg = fmt.Sprintf("{\"color\":\"%s\", \"name\":\"%s\"}", hexColorString(), h)
64 | }
65 |
66 | w.Header().Add("Access-Control-Allow-Origin", "*")
67 | w.Header().Set("Content-Type", "application/json")
68 | w.WriteHeader(status)
69 | fmt.Fprint(w, msg)
70 | }
71 |
72 | func hexColorString() string {
73 | rand.Seed(time.Now().UnixNano())
74 | i := rand.Intn(16777215) // = 0xFFFFFF the highest hex color value allowed.
75 | return "#" + fmt.Sprintf("%06s", strconv.FormatInt(int64(i), 16))
76 | }
77 |
--------------------------------------------------------------------------------
/apps/api/containers/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "net/http/httptest"
8 | "strconv"
9 | "testing"
10 | )
11 |
12 | // Because we are getting random values early tests were flaky.
13 | // Repeating test many times allowed me to see intermittent
14 | // errors
15 | var trials = 1000
16 |
17 | func TestHexColorString(t *testing.T) {
18 |
19 | for i := 0; i < trials; i++ {
20 |
21 | actual := hexColorString()
22 |
23 | if err := validateColor(actual); err != nil {
24 | t.Errorf("%v", err)
25 | }
26 |
27 | }
28 |
29 | }
30 |
31 | func validateColor(s string) error {
32 | if string(s[0]) != "#" {
33 | return fmt.Errorf("Hex color show begin with '#' got %s", string(s[0]))
34 | }
35 |
36 | if len(s) != 7 {
37 | return fmt.Errorf("Hex color should be 7 characters, got %d %s", len(s), s)
38 | }
39 |
40 | num, err := strconv.ParseInt(s[1:6], 16, 32)
41 | if err != nil {
42 | return fmt.Errorf("Error converting result to number: %v", err)
43 | }
44 |
45 | if num < 0 || num > 16777215 {
46 | return fmt.Errorf("Hex should be > 0 (000000) and 16777215 (ffffff) got: %d", num)
47 | }
48 | return nil
49 | }
50 |
51 | func TestHealthHandler(t *testing.T) {
52 | req, err := http.NewRequest("GET", "/healthz", nil)
53 | if err != nil {
54 | t.Fatal(err)
55 | }
56 |
57 | rr := httptest.NewRecorder()
58 | handler := http.HandlerFunc(health)
59 | handler.ServeHTTP(rr, req)
60 |
61 | if status := rr.Code; status != http.StatusOK {
62 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
63 | }
64 |
65 | expected := `ok`
66 | if rr.Body.String() != expected {
67 | t.Errorf("unexpected body: got %v want %v", rr.Body.String(), expected)
68 | }
69 | }
70 |
71 | func TestColorHandler(t *testing.T) {
72 |
73 | req, err := http.NewRequest("GET", "/api/color", nil)
74 | if err != nil {
75 | t.Fatal(err)
76 | }
77 |
78 | rr := httptest.NewRecorder()
79 | handler := http.HandlerFunc(color)
80 | handler.ServeHTTP(rr, req)
81 |
82 | if statusGot := rr.Code; statusGot != http.StatusOK {
83 | t.Errorf("wrong status code %s: got %d want %d", "/api/color", statusGot, http.StatusOK)
84 | }
85 |
86 | colorGot := rr.Body.String()
87 | if err := validateColor(colorGot); err != nil {
88 | t.Errorf("Invalid color got: %s, err: %v", colorGot, err)
89 | }
90 |
91 | }
92 |
93 | type result struct {
94 | Color string `json:"color"`
95 | Name string `json:"name"`
96 | }
97 |
98 | func TestColorCompleteHandler(t *testing.T) {
99 | req, err := http.NewRequest("GET", "/api/color-complete", nil)
100 | if err != nil {
101 | t.Fatal(err)
102 | }
103 |
104 | rr := httptest.NewRecorder()
105 | handler := http.HandlerFunc(colorComplete)
106 | handler.ServeHTTP(rr, req)
107 |
108 | if status := rr.Code; status != http.StatusOK {
109 | t.Errorf("wrong status code: got %v want %v", status, http.StatusOK)
110 | }
111 |
112 | var got result
113 | actual := rr.Body.Bytes()
114 | if err := json.Unmarshal(actual, &got); err != nil {
115 | t.Errorf("could not parse server response: %v", err)
116 | }
117 |
118 | if err := validateColor(got.Color); err != nil {
119 | t.Errorf("Invalid color got: %s, err: %v", got.Color, err)
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/apps/api/docker/Makefile:
--------------------------------------------------------------------------------
1 | BASEDIR = $(shell pwd)
2 |
3 | main:
4 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "$(BASEDIR)/../containers/main" "$(BASEDIR)/../containers/main.go"
5 |
6 | build: main
7 | docker build -t api "$(BASEDIR)/../containers/."
8 |
9 | serve:
10 | docker run --name=api -d -P -p 8080:8080 api
11 |
12 | clean:
13 | -docker stop api
14 | -docker rm api
15 | -docker rmi api
--------------------------------------------------------------------------------
/apps/api/kubernetes/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | BASEDIR = $(shell pwd)
15 |
16 | include ../../../Makefile.properties
17 |
18 | app: build deploy
19 |
20 | reset: clean.deployment app
21 | say "app refresh complete"
22 |
23 | reset.safe: env creds
24 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "$(BASEDIR)/../containers/main" "$(BASEDIR)/../containers/main.go"
25 | gcloud container builds submit "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO)/api
26 | -kubectl delete deployment api-deployment
27 | -kubectl run api-deployment --image=$(DOCKERREPO)/api --replicas=12 --port=8080 --labels=app=api
28 | say "app refresh complete"
29 |
30 | main:
31 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o "$(BASEDIR)/../containers/main" "$(BASEDIR)/../containers/main.go"
32 |
33 | build: env main
34 | gcloud container builds submit "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO)/api
35 |
36 | build.dockerhub: main
37 | docker build "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO):api
38 | docker push $(DOCKERREPO):api
39 |
40 | build.generic: main
41 | docker build "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO)/whackapod-api
42 | docker push $(DOCKERREPO)/whackapod-api
43 |
44 | deploy: env creds deployment service
45 |
46 | deploy.minikube: deployment service
47 |
48 | deploy.minikube.dockerhub:
49 | kubectl run api-deployment --image=$(DOCKERREPO):api --replicas=12 --port=8080 --labels=app=api
50 | kubectl expose deployment api-deployment --name=api --target-port=8080 --type=NodePort --labels="app=api"
51 |
52 | deploy.generic:
53 | kubectl run api-deployment --image=$(DOCKERREPO)/whackapod-api --replicas=12 --port=8080 --labels=app=api
54 | kubectl expose deployment api-deployment --name=api --target-port=8080 --type=NodePort --labels="app=api"
55 |
56 | test:
57 | cd "$(BASEDIR)/../containers" && go test
58 |
59 | deployment:
60 | kubectl run api-deployment --image=$(DOCKERREPO)/api --replicas=12 --port=8080 --labels=app=api
61 |
62 | service:
63 | kubectl expose deployment api-deployment --name=api --target-port=8080 --type=NodePort --labels="app=api"
64 |
65 | clean: env creds clean.deployment clean.service
66 | clean.generic: clean.deployment clean.service
67 |
68 | clean.minikube: clean.deployment clean.service
69 |
70 | clean.deployment:
71 | -kubectl delete deployment api-deployment
72 |
73 | clean.service:
74 | -kubectl delete service api
75 |
76 | retry: clean build deploy
77 |
78 | config: env
79 | @echo Creating API Yaml files based on samples and setting in your Makefile.properties
80 | @cp "$(BASEDIR)/api-deployment.sample.yaml" "$(BASEDIR)/api-deployment.yaml"
81 | $(call rewritefile,"$(BASEDIR)/api-deployment.yaml",%PROJECT%,$(PROJECT))
82 |
83 | define rewritefile
84 | @sed s/$(2)/$(3)/g <""$(1)"" >"$(BASEDIR)/.temp"
85 | @cp "$(BASEDIR)/.temp" $(1)
86 | @rm "$(BASEDIR)/.temp"
87 | endef
88 |
--------------------------------------------------------------------------------
/apps/game/containers/Dockerfile:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | FROM nginx
15 | COPY default.conf /etc/nginx/conf.d
16 | COPY default /usr/share/nginx/html
17 | RUN chmod -R 755 /usr/share/nginx/html
18 |
--------------------------------------------------------------------------------
/apps/game/containers/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 8080;
3 | server_name localhost;
4 |
5 |
6 | location / {
7 | root /usr/share/nginx/html;
8 | index index.html index.htm;
9 | }
10 |
11 | error_page 500 502 503 504 /50x.html;
12 | location = /50x.html {
13 | root /usr/share/nginx/html;
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/apps/game/containers/default/advanced.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | Whack a Pod!
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
Service
34 |
35 |
Click to deploy
36 |
Click to stop
37 |
38 | kubectl create -f whack-a-pod-deployment.yaml
39 |
40 |
41 |
View Deployment Yaml
42 |
View Service Yaml
43 |
44 |
45 |
46 |
47 |
X
48 |
Deployment Configuration
49 |
50 | apiVersion: extensions/v1beta1
51 | kind: Deployment
52 | metadata:
53 | name: api-deployment
54 | spec:
55 | replicas: 12
56 | strategy:
57 | type: RollingUpdate
58 | template:
59 | metadata:
60 | labels:
61 | app: api
62 | spec:
63 | containers:
64 | - name: "api"
65 | image: "gcr.io/projectid/api"
66 | ports:
67 | - name: "http"
68 | containerPort: 8080
69 | protocol: TCP
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
X
78 |
Service Configuration
79 |
80 | apiVersion: v1
81 | kind: Service
82 | metadata:
83 | labels:
84 | name: api
85 | name: api
86 | spec:
87 | type: LoadBalancer
88 | loadBalancerIP: 35.198.214.114
89 | ports:
90 | - name: "http"
91 | port: 80
92 | targetPort: 8080
93 | protocol: TCP
94 | selector:
95 | app: api
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | Back to Game
104 |
105 |
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/audio/countdown.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/audio/countdown.mp3
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/audio/explosion.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/audio/explosion.wav
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/audio/license.txt:
--------------------------------------------------------------------------------
1 | Sounds taken from:
2 | https://offers.adobe.com/en/na/audition/offers/audition_dlc/AdobeAuditionDLCSFX.html
3 |
4 | Which states:
5 | Your download and use of the content on this page is governed by and subject to
6 | the Adobe Software License Agreement ("EULA") for Adobe Audition and other
7 | related Adobe software, available at: http://www.adobe.com/products/eulas/
8 |
9 | Subject to the restrictions stated in the EULA, you may use, display, modify,
10 | reproduce, and distribute the associated Content File. However, you may not
11 | distribute the Content File on a standalone basis (i.e., in circumstances in
12 | which the Content File constitute the primary value of the product being
13 | distributed), and Customer may not claim any trademark rights in the Content
14 | File or derivative works thereof.
15 |
16 | http://www.adobe.com/legal/terms.html
17 |
18 | 2.5 Content Files. “Content Files” means Adobe-provided sample files such as
19 | stock images or sounds. Unless the documentation or specific license associated
20 | with the Content Files state otherwise, you may use, display, modify, reproduce,
21 | and distribute any of the Content Files. However, you may not distribute the
22 | Content Files on a stand-alone basis (i.e., in circumstances in which the
23 | Content Files constitute the primary value of the product being distributed),
24 | and you must not claim any trademark rights in the Content Files or derivative
25 | works of the Content Files.
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/audio/pop.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/audio/pop.wav
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/audio/startup.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/audio/startup.mp3
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/alert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/alert.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/background.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/background2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/background2.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/bomb_waiting2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/bomb_waiting2.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/cursor.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/cursor_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/cursor_down.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/cursor_inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/cursor_inactive.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/cursor_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/cursor_next.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/cursor_next_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/cursor_next_down.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/cursor_next_inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/cursor_next_inactive.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/cursordown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/cursordown.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/holder_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/holder_background.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/license.txt:
--------------------------------------------------------------------------------
1 | All images contained herein are licensed using Adobe Stock extended licensing.
2 |
3 | Details are posted here: https://stock.adobe.com/license-terms
4 |
5 |
6 |
7 | Date Author ID Media Type License
8 | 6/20/17, 9:59 PM © wenchiawang #96054428 Image Extended
9 | 6/20/17, 9:55 PM © sjhuls #83208628 Image Extended
10 | 6/20/17, 9:55 PM © Khvost #67401230 Image Extended
11 | 6/20/17, 9:55 PM © Daevid #24047880 Image Extended
12 | 6/20/17, 9:54 PM © studiostoks #104672324 Image Extended
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/main.css:
--------------------------------------------------------------------------------
1 | /*Copyright 2017 Google Inc. 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 | body{
16 | background: url("background2.png");
17 | background-size: cover;
18 | color: #333;
19 | font-family: 'Alegreya Sans SC', sans-serif;
20 | }
21 | .holder{
22 | padding-top: 250px;
23 | width: 1200px;
24 | margin: 180px auto 5px auto;
25 | background: url("holder_background.png");
26 | background-size: cover;
27 | border: 10px solid #333;
28 | min-height: 550px;
29 | position: relative;
30 | }
31 |
32 | .holder p{
33 | margin: 0 50px;
34 | }
35 |
36 | .report{
37 | position: absolute;
38 | left: 20px;
39 | top: -180px;
40 | width: 600px;
41 | height: 400px;
42 | padding-top: 110px;
43 | transition: color 0.2s ease;
44 | font-family: 'Rye', cursive;
45 | font-size: 2.8rem;
46 | color: #222;
47 | text-align: center;
48 | background: url("sign.png");
49 | background-size: contain;
50 | background-repeat: no-repeat;
51 | }
52 | .report span{
53 | margin: 0 100px;
54 | display: block;
55 | }
56 |
57 | .scoreboard{
58 | width: 300px;
59 | height: 200px;
60 | padding-top: 55px;
61 | font-family: 'Rye', cursive;
62 | font-size: 1.4rem;
63 | color: #222;
64 | text-align: center;
65 | background: url("sign.png");
66 | background-size: contain;
67 | background-repeat: no-repeat;
68 | position: absolute;
69 | right: 20px;
70 | top: -100px;
71 | }
72 | .scoreboard table{
73 | margin: 0 60px;
74 | }
75 | .scoreboard th{
76 | text-align: right;
77 | }
78 | .scoreboard td{
79 | font-size: 2rem;
80 | }
81 |
82 | .pods{
83 | cursor: url("cursor_inactive.png"), auto;
84 | margin: 0px 50px 0 50px;
85 | display: block;
86 | position: relative;
87 | z-index: 25;
88 | user-select: none;
89 | }
90 |
91 |
92 |
93 | .pod{
94 | cursor: url("cursor.png"), pointer;
95 | width: 78px;
96 | height: 83px;
97 | margin: 5px 5px 35px 5px;
98 | background-size: contain;
99 | background-repeat: no-repeat;
100 | display: inline-block;
101 | cursor: url("cursor.png"), pointer;
102 | text-align: center;
103 | vertical-align: bottom;
104 | font-family: 'Alegreya Sans SC', sans-serif;
105 | font-size: 1.5rem;
106 | position: relative;
107 | user-select: none;
108 | }
109 |
110 | .pod span{
111 | position: absolute;
112 | bottom: -25px;
113 | width: 100%;
114 | display: block;
115 | left: 0;
116 | text-align: center;
117 |
118 | }
119 |
120 |
121 | .pending {
122 | background-image: url("pending.png");
123 | color:yellow;
124 | }
125 |
126 | .running {
127 | background-image: url("running.png");
128 | color:#555;
129 | }
130 |
131 | .terminating {
132 | background-image: url("terminating.png");
133 | color:red;
134 | }
135 |
136 | #bomb{
137 | width: 200px;
138 | height: 199px;
139 | }
140 |
141 | .bombpanel{
142 | display:none;
143 | position: absolute;
144 | right: 0;
145 | top: 50%;
146 | width: 50%;
147 | }
148 |
149 | .bombpanel p{
150 | position: absolute;
151 | top: 40px;
152 | right:0;
153 | width: 60%;
154 | font-size: 2rem;
155 | }
156 |
157 | .alert{
158 | position: absolute;
159 | right:15%;
160 | top: -15%;
161 | width: 50%;
162 | background: url("alert.png");
163 | background-size: cover;
164 | background-repeat: no-repeat;
165 | font-family: 'Rye', cursive;
166 | font-size: 1.8rem;
167 | width: 571px;
168 | height: 526px;
169 | margin: 5px auto;
170 | text-align: center;
171 | color: #444;
172 | z-index: 50;
173 | display: none;
174 | }
175 |
176 | .alert .msg{
177 | position: absolute;
178 | top: 180px;
179 | margin: 0 140px 0 140px;
180 | }
181 |
182 | .overlay{
183 | font-family: 'Source Code Pro', monospace;
184 | font-size: 14px;
185 | background-color: #ccc;
186 | border: 1px solid red;
187 | display: none;
188 | }
189 |
190 | .overlay p{
191 | margin: 10px;
192 | padding: 10px;
193 | }
194 |
195 | .overlay table{
196 | margin: 10px;
197 | padding: 10px;
198 | }
199 |
200 |
201 | .overlay th{
202 | text-align: left;
203 | font-weight: 500;
204 | }
205 |
206 | .overlay th, .overlay td{
207 | padding: 2px 4px;
208 | }
209 |
210 | .overlay td.number{
211 | text-align: right;
212 | font-weight: 900;
213 | }
214 |
215 |
216 | .overlay-report{
217 | position:absolute;
218 | top: -180px;
219 | left: 40px;
220 | width: 550px;
221 | }
222 |
223 | .overlay-pods{
224 | position: absolute;
225 | width: 620px;
226 | right: 20px;
227 | top: 110px;
228 |
229 | }
230 |
231 | .overlay-scoreboard{
232 | position: absolute;
233 | width: 320px;
234 | right: 20px;
235 | top: -180px;
236 |
237 | }
238 |
239 | .explain{
240 | position: fixed;
241 | right: 0;
242 | top: 0;
243 | margin: 5px;
244 | padding: 5px;
245 | background-color: #ccc;
246 | border: 1px solid red;
247 | opacity: 0.5;
248 | cursor: pointer;
249 |
250 | }
251 |
252 | .explain-show{
253 | background-color: #fff;
254 | border: 1px solid red;
255 | opacity: 1;
256 |
257 | }
258 |
259 | .pods-table{
260 | margin: 0px 50px 0 50px;
261 | cursor: url("cursor_inactive.png"), auto;
262 | }
263 |
264 | .modal{
265 | min-height: 100%;
266 | width: 100%;
267 | background-color: rgba(66,66,66,.5);
268 | position: absolute;
269 | top: 0;
270 | left: 0;
271 | display: none;
272 | z-index: 98;
273 | }
274 |
275 | .modal-message{
276 | font-family: 'Source Code Pro', monospace;
277 | height: 300px;
278 | width: 600px;
279 | background-color: #fff;
280 | margin: 200px auto;
281 | padding: 20px;
282 | border: 3px solid #333;
283 | z-index: 99;
284 | }
285 |
286 | .modal-message code{
287 | background-color: #000;
288 | color: #0f0;
289 | display: block;
290 | margin: 3px 0;
291 | padding: 2px;
292 | border: 1px solid #999;
293 | }
294 |
295 |
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/next.css:
--------------------------------------------------------------------------------
1 | /*Copyright 2017 Google Inc. 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 | body{
16 | color: #333;
17 | font-family: 'Roboto', sans-serif;
18 | font-weight: 300;
19 | }
20 |
21 |
22 | .holder{
23 | padding-top: 250px;
24 | width: 60%;
25 | margin: 50px 0 5px 0;
26 | background-size: cover;
27 | position: relative;
28 | border-radius: 4px;
29 | overflow: 0;
30 | }
31 |
32 | .holder p{
33 | margin: 0 25px;
34 | }
35 |
36 | .holder p:first-of-type{
37 | margin-top: -90px;
38 | }
39 |
40 |
41 | .report{
42 | position: absolute;
43 | left: 70px;
44 | top: -40px;
45 | width: 150px;
46 | height: 135px;
47 | padding-top: 140px;
48 | transition: color 0.2s ease;
49 | -webkit-transition: -webkit-filter 0.2s ease;
50 | color: #222;
51 | text-align: center;
52 | background: url("service_inactive.png");
53 | background-size: contain;
54 | background-repeat: no-repeat;
55 | -webkit-filter: drop-shadow(2px 2px 3px #333);
56 | }
57 |
58 | #report_message{
59 | position: absolute;
60 | top: 120px;
61 | left: 45px;
62 | font-weight: 700;
63 | }
64 |
65 | .report span{
66 | font-weight: 900;
67 | display: block;
68 | width: 300px;
69 | position: relative;
70 | top: 15px;
71 | left: -75px;
72 | }
73 |
74 | .service_up{
75 | background: url("service_up.png");
76 | background-size: contain;
77 | background-repeat: no-repeat;
78 | }
79 |
80 | .service_down{
81 | background: url("service_down.png");
82 | background-size: contain;
83 | background-repeat: no-repeat;
84 | }
85 |
86 |
87 |
88 | .scoreboard{
89 | width: 200px;
90 | height: 100px;
91 | padding-top: 25px;
92 | color: #222;
93 | text-align: center;
94 | position: absolute;
95 | right: 20px;
96 | top: -35px;
97 | border-radius: 2px;
98 |
99 | }
100 | .scoreboard table{
101 | margin: 0 60px;
102 | }
103 | .scoreboard th{
104 | text-align: right;
105 | }
106 | .scoreboard td{
107 | font-size: 2rem;
108 | }
109 |
110 |
111 |
112 |
113 | .pod{
114 | cursor: url("cursor_next.png"), pointer;
115 | width: 80px;
116 | height: 80px;
117 | margin: 5px 5px 35px 5px;
118 | background-color: #000000;
119 | color: #fff;
120 | display: inline-block;
121 | text-align: center;
122 | vertical-align: bottom;
123 | position: relative;
124 | user-select: none;
125 | border-radius: 50px;
126 | border: 5px solid #ccc;
127 | transition: border-color .5s, border-width .5s, -webkit-filter 0.5s ;
128 | }
129 |
130 | .pod span{
131 | position: relative;
132 | top: 30px;
133 | width: 100%;
134 | display: block;
135 | left: 0;
136 | text-align: center;
137 | font-weight: 700;
138 | }
139 |
140 | .pods-table{
141 | margin: -110px 50px 0 50px;
142 | width: 380px;
143 | cursor: url("cursor_next_inactive.png"), auto;
144 | position: relative;
145 | }
146 |
147 |
148 |
149 | .pending {
150 | background-color: #fabb05;
151 | cursor: url("cursor_next_inactive.png"), auto;
152 | -webkit-filter: drop-shadow(1px 1px 1px rgba(66,66,66,.8));
153 | }
154 |
155 | .running {
156 | background-color:#0f9058;
157 | cursor: url("cursor_next.png"), auto;
158 | -webkit-filter: drop-shadow(4px 4px 5px rgba(66,66,66,.5));
159 | }
160 |
161 | .hit {
162 | -webkit-filter: drop-shadow(1px 1px 1px rgba(66,66,66,.8));
163 | cursor: url("cursor_next_down.png"), auto;
164 | }
165 |
166 | .running:active {
167 | -webkit-filter: drop-shadow(1px 1px 1px rgba(66,66,66,.8));
168 | cursor: url("cursor_next_down.png"), auto;
169 | }
170 |
171 | .terminating {
172 | background-color: #ccc;
173 | cursor: url("cursor_next_inactive.png"), auto;
174 | -webkit-filter: drop-shadow(1px 1px 1px rgba(66,66,66,.8));
175 | }
176 |
177 | .pods-explain{
178 | position: absolute;
179 | top: 430px;
180 | left: 500px;
181 | display: none;
182 | }
183 |
184 | th.pending, th.running, th.terminating{
185 | color: #fff;
186 | padding: 5px 10px;
187 | -webkit-filter: none;
188 | }
189 |
190 | th.pending{
191 | background-color: #fabb05;
192 | }
193 | th.running{
194 | background-color:#0f9058;
195 | }
196 | th.terminating{
197 | background-color: #ea4335;
198 | }
199 |
200 | #bomb{
201 | top: 40px;
202 | width: 330px;
203 | height: 311px;
204 | position: relative;
205 | margin: 0 auto;
206 | -webkit-animation-name: orangeShadowPulse;
207 | -webkit-animation-duration: 1s;
208 | -webkit-animation-iteration-count: infinite;
209 | }
210 |
211 | @-webkit-keyframes orangeShadowPulse {
212 | from { -webkit-filter: drop-shadow(3px 3px 5px #E17C26); }
213 | 50% { -webkit-filter: drop-shadow(3px 3px 5px #FABB05); }
214 | to { -webkit-filter: drop-shadow(3px 3px 5px #E17C26); }
215 | }
216 |
217 | @-webkit-keyframes orangeTextPulse {
218 | from { color: #E17C26; }
219 | 50% { color: #FABB05; }
220 | to { color: #E17C26; }
221 | }
222 |
223 | .bombpanel{
224 | display:none;
225 | position: absolute;
226 | right: -10%;
227 | top: 30px;
228 | width: 400px;
229 | }
230 |
231 | .bombpanel p{
232 | position: relative;
233 | text-align: center;
234 | left:40px;
235 | width: 50%;
236 | top: 40px;
237 | font-size: 1.2rem;
238 | font-weight: 900;
239 | font-style: italic;
240 | -webkit-animation-name: orangeTextPulse;
241 | -webkit-animation-duration: 1s;
242 | -webkit-animation-iteration-count: infinite;
243 | }
244 |
245 | .modal{
246 | min-height: 100%;
247 | width: 100%;
248 | background-color: rgba(66,66,66,.5);
249 | position: absolute;
250 | top: 0;
251 | left: 0;
252 | display: none;
253 | z-index: 98;
254 | }
255 |
256 | .modal-message{
257 | font-weight: 300;
258 | width: 500px;
259 | background-color: #fff;
260 | margin: 100px auto;
261 | padding: 20px;
262 | border: 3px solid #333;
263 | z-index: 99;
264 | box-shadow: 3px 3px 5px rgba(0,0,0,.5);
265 | border-radius: 4px;
266 | position: relative;
267 | }
268 |
269 | code{
270 | background-color: #000;
271 | color: #0f0;
272 | display: block;
273 | margin: 3px 0;
274 | padding: 2px;
275 | border: 1px solid #999;
276 | }
277 |
278 | .overlay{
279 | font-family: 'Source Code Pro', monospace;
280 | font-size: 14px;
281 | background-color: #ccc;
282 | border: 1px solid red;
283 | display: none;
284 | }
285 |
286 | .overlay p{
287 | margin: 10px;
288 | padding: 10px;
289 | }
290 |
291 | .overlay table{
292 | margin: 10px;
293 | padding: 10px;
294 | }
295 |
296 |
297 | .overlay th{
298 | text-align: left;
299 | font-weight: 500;
300 | }
301 |
302 | .overlay th, .overlay td{
303 | padding: 2px 4px;
304 | }
305 |
306 | .overlay td.number{
307 | text-align: right;
308 | font-weight: 900;
309 | }
310 |
311 |
312 | .overlay-report{
313 | position:absolute;
314 | top: -180px;
315 | left: 40px;
316 | width: 550px;
317 | }
318 |
319 | .overlay-pods{
320 | position: absolute;
321 | width: 620px;
322 | right: 20px;
323 | top: 110px;
324 |
325 | }
326 |
327 | .overlay-scoreboard{
328 | position: absolute;
329 | width: 320px;
330 | right: 20px;
331 | top: -180px;
332 |
333 | }
334 |
335 |
336 | .alert{
337 | position: absolute;
338 | right: 10px;
339 | top: 70px;
340 | width: 360px;
341 | height: 30px;
342 | margin: 5px auto;
343 | text-align: center;
344 | color: #444;
345 | z-index: 50;
346 | display: none;
347 | border: 2px solid #4285F4;
348 | background-color: #f0f0f0;
349 | box-shadow: 3px 3px 5px rgba(66,66,66,.5);
350 | border-radius: 4px;
351 | font-weight: 900;
352 | font-size: 1.3rem;
353 | padding: 20px;
354 |
355 | }
356 |
357 |
358 | .totals{
359 | font-weight: 700;
360 | }
361 |
362 | #logwindow{
363 | width: 40%;
364 | margin: 0;
365 | position: absolute;
366 | right: 0;
367 | top: 0;
368 | height: 100%;
369 | background-color: #000000;
370 | color: #0f0;
371 | border-radius: 4px;
372 | border: 1px solid mediumslateblue;
373 | overflow: hidden;
374 | font-size: .5rem;
375 | }
376 |
377 | pre{
378 | font-family: 'Source Code Pro', monospace;
379 | }
380 |
381 | pre .phase{
382 | font-weight: 900;
383 | background-color: #0f0;
384 | color: #000;
385 | }
386 |
387 | button{
388 | margin : 0;
389 | padding : 0;
390 | border : 0;
391 | background : transparent;
392 | font-family : inherit;
393 | font-size : 1em;
394 | cursor : pointer;
395 | }
396 |
397 | button::-moz-focus-inner{
398 | padding : 0;
399 | border : 0;
400 | }
401 |
402 | button:focus {outline:0;}
403 |
404 | button{
405 | cursor: url("cursor_next.png"), pointer;
406 | width: 80px;
407 | height: 80px;
408 | margin: 5px 5px 5px 5px;
409 | background-color: #000000;
410 | color: #fff;
411 | display: inline-block;
412 | text-align: center;
413 | vertical-align: bottom;
414 | position: relative;
415 | user-select: none;
416 | border-radius: 50px;
417 | border: 5px solid #ddd;
418 | background-color: #4285F4;
419 | position: relative;
420 | }
421 |
422 | button#deploy-end{
423 | background-color: #ea4335;
424 | }
425 |
426 | button.small{
427 | cursor: pointer;
428 | position: absolute;
429 | right: -18px;
430 | top: -30px;
431 | width: 50px;
432 | height: 50px;
433 | margin: 5px 5px 5px 5px;
434 | background-color: #ea4335;
435 | }
436 |
437 | .modal button.small{
438 | cursor: pointer;
439 | position: absolute;
440 | right: -15px;
441 | top:-15px;
442 | width: 60px;
443 | height: 60px;
444 | margin: 5px 5px 5px 5px;
445 | background-color: #ea4335;
446 | }
447 |
448 | button.small.reset{
449 | background-color: #4285F4;
450 | }
451 |
452 | #nodes{
453 | display: table;
454 | border-spacing: 15px;
455 | }
456 |
457 | .node{
458 | border: 5px solid #ddd;
459 | border-radius: 5px;
460 | position: relative;
461 | margin-bottom: 5px;
462 | width: 45%;
463 | display: inline-block;
464 | vertical-align: top;
465 | margin-right: 20px;
466 | position:relative;
467 | padding-bottom: 100px;
468 | display: table-cell;
469 | }
470 |
471 | .node p {
472 | position: relative;
473 | top: 100px;
474 | }
475 |
476 |
477 | .node .pod{
478 | top: 100px;
479 | }
480 |
481 | #endpoint{
482 | position: absolute;
483 | left: 250px;
484 | top: 20px;
485 | width: 450px;
486 | }
487 |
488 | #service{
489 | border: 5px dotted #ddd;
490 | border-radius: 5px;
491 | position: relative;
492 | top: -230px;
493 | height: 160px;
494 | width: 92%;
495 | margin-bottom: -220px;
496 |
497 | }
498 |
499 | #deploy-end{
500 | display: none;
501 | }
502 |
503 | .kube-label{
504 | font-family: 'Source Code Pro', monospace;
505 | position: absolute;
506 | bottom: 2px;
507 | right: 2px;
508 | }
509 |
510 | .pod .kube-label{
511 | bottom: 2px;
512 | right: 25px;
513 | font-size: 10px;
514 | }
515 |
516 |
517 | dl{
518 | font-size: 18px;
519 | }
520 | dt{
521 | font-weight: bold;
522 | font-family: 'Source Code Pro', monospace;
523 |
524 | }
525 | dd{
526 | width: 700px;
527 | margin-bottom: 5px;
528 | }
529 |
530 | .responder{
531 | border-color: #FABB05;
532 | animation: pulse 2s infinite;
533 | -webkit-filter: drop-shadow(4px 4px 5px rgba(33,33,33,.8));
534 | }
535 |
536 |
537 | @keyframes pulse {
538 | 0% {
539 | border-color: #FABB05;
540 | }
541 | 50% {
542 | border-color: #FFFFFF;
543 | }
544 | 100% {
545 | border-color: #FABB05;
546 | }
547 |
548 | }
549 |
550 |
551 | nav a{
552 | position: fixed;
553 | display: block;
554 | top: 10px;
555 | right: 41%;
556 | z-index: 100;
557 | border: 1px solid #ccc;
558 | background-color: #333;
559 | padding: 3px 10px;
560 | color: #FFF;
561 | }
562 |
563 | #deployment{
564 | width: 500px;
565 | display: inline-block;
566 | vertical-align: middle;
567 | margin-top: -60px;
568 | }
569 |
570 | .text-reveal{
571 | height: auto;
572 | width: auto;
573 | border-radius: 8px;
574 | padding: 10px 20px;
575 | cursor: pointer;
576 | border-width: 3px;
577 | }
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/pending.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/pending.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/pending_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/pending_next.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/running.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/running.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/running_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/running_next.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/service_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/service_down.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/service_inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/service_inactive.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/service_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/service_up.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/sign.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/sign.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/terminating.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/terminating.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/css/terminating_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/css/terminating_next.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/img/bomb_explode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/img/bomb_explode.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/img/bomb_explode_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/img/bomb_explode_next.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/img/bomb_waiting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/img/bomb_waiting.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/img/bomb_waiting_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/apps/game/containers/default/assets/img/bomb_waiting_next.png
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/img/license.txt:
--------------------------------------------------------------------------------
1 | All images contained herein are licensed using Adobe Stock extended licensing.
2 |
3 | Details are posted here: https://stock.adobe.com/license-terms
4 |
5 |
6 |
7 | Date Author ID Media Type License
8 | 6/20/17, 9:59 PM © wenchiawang #96054428 Image Extended
9 | 6/20/17, 9:55 PM © sjhuls #83208628 Image Extended
10 | 6/20/17, 9:55 PM © Khvost #67401230 Image Extended
11 | 6/20/17, 9:55 PM © Daevid #24047880 Image Extended
12 | 6/20/17, 9:54 PM © studiostoks #104672324 Image Extended
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/js/advanced.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. 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 | var api = new API(servicehost);
16 | var logwindow = new LOGWINDOW();
17 | var deploymentAPI = new DEPLOYMENTAPI(adminhost, logwindow);
18 | var pods = new PODS();
19 | var game = new GAME();
20 | var score = new SCORE();
21 | var nodes = [];
22 | var pods_shown = [];
23 | var fails_threshold = 20;
24 |
25 |
26 | document.addEventListener('DOMContentLoaded', function() {
27 | api.timeout = 10000;
28 | deploymentAPI.Delete();
29 | deploymentAPI.ResetNodes();
30 | setReport("");
31 | $("#deploy-start").click(startDeployment);
32 | $("#deploy-end").click(endDeployment);
33 | $("#endpoint").html(api.URL());
34 | $("#show-pod-yaml").click(showPodModal);
35 | $("#close-pod-modal").click(hidePodModal);
36 | $("#show-service-yaml").click(showServiceModal);
37 | $("#close-service-modal").click(hideServiceModal);
38 | });
39 |
40 | function showModal(id){
41 | var modal = $(id);
42 | modal.fadeIn('slow');
43 | }
44 |
45 | function hideModal(id){
46 | var modal = $(id);
47 | modal.fadeOut();
48 | }
49 |
50 | function showPodModal(e){
51 | showModal("#pod-yaml");
52 | }
53 |
54 | function hidePodModal(e){
55 | hideModal("#pod-yaml");
56 | }
57 |
58 | function showServiceModal(e){
59 | showModal("#service-yaml");
60 | }
61 |
62 | function hideServiceModal(e){
63 | hideModal("#service-yaml");
64 | }
65 |
66 | function setReport(msg, color){
67 | if (typeof color == "undefined") color = "#333333";
68 | var report = $(".report");
69 |
70 | report.css("-webkit-filter", "drop-shadow(2px 2px 3px " + color + ")");
71 | report.css("color", color);
72 | var msgholder = $("#report_message");
73 | if (msgholder.length == 0){
74 | report.after('' + msg + '
')
75 | } else {
76 | msgholder.html(msg);
77 | }
78 |
79 |
80 | if(game.GetState() == "running"){
81 | if(game.IsServiceDown()) {
82 | $(".report").addClass("service_down");
83 | $(".report").removeClass("service_up");
84 | } else{
85 | $(".report").addClass("service_up");
86 | $(".report").removeClass("service_down");
87 | }
88 | }
89 |
90 | }
91 |
92 | function startDeployment(){
93 | $("#deploy-start").hide();
94 | $("#deploy-start").css('z-index', 1);
95 | $("#deploy-end").show();
96 | $("#deploy-end").css('z-index', 300);
97 | deploymentAPI.Create(initGame,genericError);
98 | setReport("");
99 | $("#deployment").html("kubectl delete -f whack-a-pod-deployment.yaml")
100 | }
101 |
102 | function initGame(e){
103 | game.Start(getColor, showScore, getPods, getTimeLeft);
104 | logwindow.Log(e);
105 |
106 | }
107 |
108 | function getColor(){
109 | api.ColorComplete(handleColor, handleColorError)
110 | }
111 |
112 | function handleColor(e){
113 | if (game.GetState() == "started"){
114 | game.Init();
115 | setTimeout(function(){$(".pods-explain").fadeOut();}, 3000);
116 | }
117 |
118 | if (game.GetState() == "running"){
119 | game.SetServiceUp();
120 | setReport("Service call result: "+ e.color, e.color);
121 | $(".responder").removeClass("responder");
122 | $("#"+e.name).addClass("responder");
123 |
124 | }
125 | if (api.ResetFails()){
126 | console.log("Soft service has recovered.");
127 | }
128 | }
129 |
130 | function handleColorError(e,textStatus, errorThrown){
131 | if (game.GetState() == "running") {
132 | if (api.IsHardFail()){
133 | console.log("Hard service fail.");
134 | setReport("Kubernetes service is DOWN!", "#FF0000");
135 | $(".responder").removeClass("responder");
136 | alertYouKilledIt();
137 | } else {
138 | console.log("Soft service fail. Retry");
139 | }
140 | }
141 | }
142 |
143 | function getPods(){
144 | deploymentAPI.Get(handlePods, handlePodsError);
145 | }
146 |
147 | function handlePods(e,b){
148 |
149 | if (game.GetState() == "done") {
150 | return;
151 | }
152 |
153 | drawPods(e);
154 |
155 | }
156 |
157 | function drawPods(pods){
158 |
159 | var pods_active = [];
160 | //create node UI if it doesn't exist
161 | for (var i = 0; i < pods.items.length; i++){
162 | var pod = new POD(pods.items[i]);
163 | logwindow.Log((pod));
164 |
165 | if ((pod.host == null) || (pod.host.length == 0)){
166 | continue;
167 | }
168 |
169 | pods_active.push(pod.name);
170 |
171 | if (nodes.indexOf(pod.host) < 0){
172 | nodes.push(pod.host);
173 | createNodeUI(pod.host);
174 | }
175 |
176 | if (pods_shown.indexOf(pod.name) < 0){
177 | pods_shown.push(pod.name);
178 | createPodUI(pod);
179 | }
180 |
181 | var $pod = $("#"+ pod.name);
182 | $pod.addClass(pod.phase);
183 |
184 | if (pod.phase == "running") {
185 | if (!$pod.hasClass("bound")){
186 | $pod.click(whackHandler);
187 | $pod.addClass("bound");
188 | }
189 | } else{
190 | $pod.click();
191 | }
192 |
193 | if (pod.ShouldRemove()){
194 | $pod.remove();
195 | }
196 |
197 | }
198 | for (var i = 0; i < pods_shown.length; i++){
199 | if (pods_active.indexOf(pods_shown[i]) < 0){
200 | $("#"+ pods_shown[i]).remove();
201 | }
202 | }
203 |
204 | }
205 |
206 |
207 | function createPodUI(pod){
208 | var hostID = "node_"+pod.host;
209 |
210 | var div = document.createElement("div");
211 | div.id = pod.name;
212 | div.dataset.selflink = pod.selflink;
213 | div.classList.add("pod");
214 |
215 | var span = document.createElement("span");
216 | span.innerHTML = pod.shortname;
217 | span.dataset.selflink = pod.selflink;
218 | div.append(span);
219 |
220 | var label = document.createElement("div");
221 | label.classList.add("kube-label");
222 | label.classList.add("kube-pod");
223 | label.innerHTML= "Pod";
224 | div.append(label);
225 |
226 |
227 | $("#pod-" + pod.holder).append(div);
228 | $("#"+ hostID).append(div);
229 |
230 | // logwindow.Log((pod));
231 |
232 | }
233 |
234 |
235 | function createNodeUI(name){
236 | var id = "node_"+name;
237 | var label = name.split("-")[name.split("-").length -1 ];
238 | var $div = $('');
239 | var $holder = $("#nodes");
240 | $div.appendTo("#nodes")
241 | $killbtn = $("#kill-" + id)
242 |
243 | if(name=="minikube"){
244 | $killbtn.hide();
245 | } else{
246 | $killbtn.click(killNode);
247 | $killbtn.data("node", name);
248 | }
249 |
250 | }
251 |
252 | function killNode(e){
253 | var $killbtn = $("#" + e.currentTarget.id);
254 | var node = $killbtn.data("node");
255 | deploymentAPI.DrainNode(node);
256 | $killbtn.click(resetNode);
257 | $killbtn.addClass("reset");
258 | $killbtn.text("+");
259 | }
260 |
261 |
262 | function resetNode(e){
263 | var $killbtn = $("#" + e.currentTarget.id);
264 | var node = $killbtn.data("node");
265 | deploymentAPI.UncordonNode(node);
266 | $killbtn.removeClass("reset");
267 | $killbtn.text("X");
268 | $killbtn.click(killNode);
269 |
270 | }
271 |
272 | function handlePodsError(e){
273 | // $(".pods").html("");
274 | if (typeof e != "string"){
275 | console.log("Error getting pods:", e.statusText);
276 | } else{
277 | console.log("Error getting pods:", e);
278 | }
279 |
280 | }
281 |
282 | function genericError(e){
283 | console.log("Error: ", e);
284 | }
285 |
286 | function showScore(){
287 | }
288 |
289 | function getTimeLeft(){
290 | }
291 |
292 | function alertYouKilledIt(){
293 | if (!game.IsServiceDown() && game.GetState() == "running"){
294 | console.log("Killed it.");
295 | game.SetServiceDown();
296 | score.KnockDown()
297 | }
298 | }
299 |
300 |
301 | function whackHandler(e){
302 | if (e.target.id == ""){
303 | $("#" + e.target.parentNode.id ).addClass("terminating");
304 | } else{
305 | $("#" + e.target.id ).addClass("terminating");
306 | }
307 |
308 | killPod(e.target.dataset.selflink)
309 | }
310 |
311 |
312 | function killPod(selflink){
313 | deploymentAPI.DeletePod(selflink, killHandler, podError);
314 | }
315 |
316 | function killHandler(e){
317 | logwindow.Log(e);
318 | }
319 |
320 | function podError(e){
321 |
322 | // console.log("Pod already gone? :", e);
323 | }
324 |
325 | function endDeployment(){
326 | deploymentAPI.Delete();
327 | game.Stop();
328 | setReport("");
329 | location.reload();
330 | }
331 |
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/js/lib.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. 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 | var servicehost = "";
16 | var adminhost = "";
17 |
18 |
19 | function SCORE(){
20 | var total = 0;
21 | var pods = 0;
22 | var knockdowns = 0;
23 |
24 | this.KnockDown = function(){
25 | knockdowns++;
26 | total += 100;
27 | };
28 |
29 | this.KillPod = function(){
30 | pods++;
31 | total++;
32 | };
33 |
34 | this.GetTotal = function(){
35 | return total;
36 | };
37 |
38 | this.GetPods = function(){
39 | return pods;
40 | };
41 |
42 | this.GetKnockDowns = function(){
43 | return knockdowns;
44 | };
45 |
46 | }
47 |
48 | function PODS(){
49 | this.max = 12;
50 | var podsArray = [];
51 | var DoNotReAdd = new Object();
52 |
53 | var initPods = function(){
54 | podsArray = [];
55 | for (var i = 0; i < this.max; i++){
56 | podsArray[i] = "";
57 | }
58 | };
59 |
60 | this.SetMax = function(max){
61 | this.max = max;
62 | initPods();
63 | };
64 | initPods();
65 |
66 | this.FindEmpty = function(){
67 | for (var i = 0; i < this.max; i++){
68 | if (typeof podsArray[i] !== 'object'){
69 | return i;
70 | }
71 | }
72 | return -1;
73 | };
74 |
75 | this.IsPodPresent = function(name){
76 | for (var i = 0; i < podsArray.length; i++){
77 | if (typeof podsArray[i] !== 'object'){
78 | continue;
79 | }
80 |
81 | if(name == podsArray[i].name){
82 | return true;
83 | }
84 | }
85 | return false;
86 | };
87 |
88 | this.Get = function(input){
89 | if (typeof input == "string") {
90 | var name = input;
91 | for (var i = 0; i < podsArray.length; i++){
92 | if (typeof podsArray[i] !== 'object'){
93 | continue;
94 | }
95 |
96 | if(name == podsArray[i].name){
97 | return podsArray[i];
98 | }
99 | }
100 | }
101 |
102 | if (typeof input == "number") {
103 | return podsArray[input];
104 | }
105 |
106 | return ;
107 | };
108 |
109 | this.Set = function(pod){
110 | if (DoNotReAdd.hasOwnProperty('pod.name')){
111 | return;
112 | }
113 | podsArray[pod.holder] = pod;
114 | };
115 |
116 | this.Delete = function(pod){
117 | DoNotReAdd[pod.name] = true;
118 | podsArray[pod.holder] = "";
119 | };
120 |
121 | this.Count = function(){
122 | return podsArray.length;
123 | };
124 |
125 | this.Add = function(json){
126 | var name = json.metadata.name;
127 | var pod = new POD(json);
128 |
129 | if (this.IsPodPresent(name)){
130 | pod = this.Get(name);
131 | } else{
132 | var target = this.FindEmpty();
133 | pod.holder = target;
134 | }
135 |
136 | pod.SetPhase(json);
137 |
138 | if (pod.holder != -1){
139 | this.Set(pod);
140 | }
141 | }
142 |
143 |
144 |
145 | }
146 |
147 | function NODE(json){
148 | this.name = json.metadata.name;
149 | this.selflink = json.metadata.selfLink;
150 | this.type = "Node";
151 | this.status = "Ready";
152 |
153 | if (typeof json.spec.unschedulable != "undefined"){
154 | this.status = "Ready,SchedulingDisabled";
155 | }
156 |
157 |
158 | this.SetShortName = function(){
159 | var nodenameArr = this.name.split("-");
160 | this.shortname = nodenameArr[nodenameArr.length-1];
161 | }
162 |
163 | this.SetShortName();
164 | }
165 |
166 |
167 | function POD(json){
168 |
169 | this.selflink = json.metadata.selfLink;
170 | this.type = "Pod";
171 | this.terminateThreshold = 1000;
172 | this.phase = "";
173 | this.holder = "";
174 | this.shortname = "";
175 |
176 | if (typeof json.spec != "undefined" && typeof json.spec.nodeName != "undefined"){
177 | this.host = json.spec.nodeName;
178 | }
179 |
180 | if (typeof json.metadata.name != "undefined"){
181 | this.name = json.metadata.name;
182 | }
183 |
184 | if (typeof json.status != "undefined"){
185 | this.hostIP = json.status.hostIP;
186 | }
187 |
188 |
189 | if (typeof this.host == "undefined"){
190 | this.host = "";
191 | }
192 |
193 | this.phase ="";
194 | this.startTerminate ="";
195 |
196 | this.SetShortName = function(){
197 | var nodenameArr = this.name.split("-");
198 | this.shortname = nodenameArr[nodenameArr.length-1];
199 | }
200 |
201 | this.ShouldRemove = function(){
202 | if (this.phase == "terminating"){
203 | var now = new Date();
204 | if ( now - this.startTerminate > this.terminateThreshold){
205 | return true;
206 | }
207 | }
208 | return false;
209 | }
210 |
211 | this.SetPhase = function(json){
212 | var podPhase = json.status.phase ? json.status.phase.toLowerCase() : '';
213 | this.phase = podPhase;
214 |
215 | if ((podPhase != "terminating") && (typeof json.metadata.deletionTimestamp != "undefined")) {
216 | this.phase = "terminating";
217 | this.startTerminate = new Date();
218 | }
219 | }
220 | this.SetShortName();
221 | this.SetPhase(json);
222 | }
223 |
224 | function PODSUI(pods, logwindow){
225 | var pods = pods;
226 | if (typeof(logwindow)==='undefined') logwindow = new LOGWINDOW();
227 |
228 | var alreadyShown = new Object();
229 | alreadyShown.terminating = new Object();
230 |
231 | this.ClearTerminating = function(){
232 | for (var i = 0; i < pods.Count(); i++){
233 | var podObj = pods.Get(i);
234 | if (podObj.ShouldRemove() ){
235 | pods.Delete(podObj);
236 | var poddiv = document.getElementById(podObj.name);
237 | if (poddiv != null){
238 | poddiv.parentNode.removeChild(poddiv);
239 | }
240 |
241 | }
242 | }
243 | }
244 |
245 | this.ClearMissing = function(podNames){
246 | var podsDOM = document.querySelectorAll('.pod'), i;
247 | for (i = 0; i < podsDOM.length; ++i) {
248 | if (podNames.lastIndexOf(podsDOM[i].id) < 0){
249 | pods.Delete(podsDOM[i].id);
250 | //TODO: uncomment.
251 | podsDOM[i].parentNode.removeChild(podsDOM[i]);
252 | }
253 | }
254 | }
255 |
256 | this.ClearAll = function(){
257 | for (var i = 0; i < pods.Count(); i++){
258 | var podObj = pods.Get(i);
259 | var poddiv = document.getElementById(podObj.name);
260 | if (poddiv){
261 | poddiv.parentNode.removeChild(poddiv);
262 | }
263 | }
264 | }
265 |
266 | this.AddPod = function(pod, hitHandler){
267 |
268 | var div = document.getElementById(pod.name);
269 |
270 | if (!div){
271 | div = document.createElement("div");
272 | div.id = pod.name;
273 | div.dataset.selflink = pod.selflink;
274 | div.classList.add("pod");
275 | var span = document.createElement("span");
276 | span.innerHTML = pod.shortname;
277 | span.dataset.selflink = pod.selflink;
278 | div.append(span);
279 | $("#pod-" + pod.holder).append(div);
280 | logwindow.Log(pod);
281 | }
282 |
283 | div.classList.add(pod.phase);
284 |
285 | if (pod.phase == "running"){
286 | div.addEventListener("click", hitHandler);
287 | } else{
288 | div.removeEventListener("click", hitHandler);
289 | }
290 |
291 | }
292 |
293 | this.DrawPods = function(json, whackHandler){
294 |
295 | var podNames = [];
296 | for (var i = 0; i < json.items.length; i++){
297 | podNames.push(json.items[i].metadata.name);
298 | }
299 |
300 | this.ClearTerminating();
301 | this.ClearMissing(podNames);
302 |
303 | for (var i = 0; i < json.items.length; i++){
304 | pods.Add(json.items[i]);
305 | }
306 |
307 | for (var i = 0; i < pods.Count(); i++){
308 | var pod = pods.Get(i);
309 | this.AddPod(pod,whackHandler);
310 | logwindow.Log(pod);
311 | }
312 | }
313 | }
314 |
315 | function API(hostname){
316 |
317 | this.debug = false;
318 | this.fails = 0;
319 | this.fail_threshold = 2;
320 | var apihostname = window.location.host;
321 | this.timeout = 5000;
322 | var apiprotocol = window.location.protocol + "//";
323 | if (apihostname.length == 0){
324 | apiprotocol ="";
325 | }
326 | var uri_color = "/api/color/";
327 | var uri_color_complete = "/api/color-complete/";
328 |
329 | var ajaxProxy = function(url, successHandler, errorHandler, timeout) {
330 | timeout = typeof timeout !== 'undefined' ? timeout : this.timeout;
331 | var connections = $.ajax({
332 | url: url,
333 | success: successHandler,
334 | error: errorHandler,
335 | timeout: timeout
336 |
337 | });
338 | if (this.debug){
339 | console.log("Called: ", url);
340 | }
341 | };
342 |
343 | var getColorURI = function(){
344 | return apiprotocol + apihostname + uri_color;
345 | };
346 |
347 | var getColorCompleteURI = function(){
348 | return apiprotocol + apihostname + uri_color_complete;
349 | };
350 |
351 | this.Color = function(successHandler, errorHandler){
352 | ajaxProxy(getColorURI(), successHandler, errorHandler, 400);
353 | };
354 |
355 | this.ColorComplete = function(successHandler, errorHandler){
356 | ajaxProxy(getColorCompleteURI(), successHandler, errorHandler, 400);
357 | };
358 |
359 | this.URL = getColorURI;
360 |
361 | this.IsHardFail = function(){
362 | if (this.fails > this.fail_threshold){
363 | return true;
364 | } else {
365 | this.fails++;
366 | return false;
367 | }
368 | };
369 |
370 | this.ResetFails = function(){
371 | if (this.fails != 0){
372 | this.fails = 0;
373 | return true;
374 | }
375 | return false;
376 | };
377 |
378 | }
379 |
380 | function DEPLOYMENTAPI(hostname, logwindow){
381 | if (typeof(logwindow)==='undefined') logwindow = new LOGWINDOW();
382 |
383 | this.debug = false;
384 | var apihostname = window.location.host;
385 | this.timeout = 5000;
386 | var apiprotocol = window.location.protocol + "//";
387 | if (apihostname.length == 0){
388 | apiprotocol ="";
389 | }
390 | var uri_getnodes = "/admin/k8s/nodes/get";
391 | var uri_get = "/admin/k8s/pods/get";
392 | var uri_delete = "/admin/k8s/deployment/delete";
393 | var uri_create = "/admin/k8s/deployment/create";
394 | var uri_deletepod = "/admin/k8s/pod/delete?pod=";
395 | var uri_drain = "/admin/k8s/node/drain?node=";
396 | var uri_uncordon = "/admin/k8s/node/uncordon?node=";
397 |
398 |
399 | var getPodsURI = function(){
400 | return apiprotocol + apihostname + uri_get;
401 | };
402 |
403 | var getNodesURI = function(){
404 | return apiprotocol + apihostname + uri_getnodes;
405 | };
406 |
407 | var getDeleteURI = function(){
408 | return apiprotocol + apihostname + uri_delete;
409 | };
410 |
411 | var getDeletePodURI = function(){
412 | return apiprotocol + apihostname + uri_deletepod;
413 | };
414 |
415 | var getCreateURI = function(){
416 | return apiprotocol + apihostname + uri_create;
417 | };
418 |
419 | var getDrainURI = function(){
420 | return apiprotocol + apihostname + uri_drain;
421 | };
422 |
423 | var getUncordonURI = function(){
424 | return apiprotocol + apihostname + uri_uncordon;
425 | };
426 |
427 | var success = function(e){
428 | if (typeof(logwindow)!='undefined') {
429 | logwindow.Log(e);
430 | }
431 | };
432 |
433 | var error = function(e){
434 | if (typeof e.status != "undefined" && e.status == 404){
435 | console.log("Item not found which in most cases is expected.");
436 | } else{
437 | console.log("Failure: " , e);
438 | }
439 |
440 | };
441 |
442 | var ajaxProxy = function(url) {
443 | $.ajax({
444 | url: url,
445 | success: success,
446 | error: error,
447 | timeout: this.timeout
448 |
449 | });
450 | if (this.debug){
451 | console.log("Called: ", url);
452 | }
453 |
454 | };
455 |
456 | this.Delete = function(){
457 | ajaxProxy(getDeleteURI());
458 | };
459 |
460 | this.DeletePod = function(pod, successHandler, errorHandler){
461 | var url = getDeletePodURI() + pod;
462 | $.ajax({
463 | url: url,
464 | success: successHandler,
465 | error: errorHandler,
466 | timeout: this.timeout
467 | });
468 | if (this.debug){
469 | console.log("Called: ", url);
470 | }
471 | };
472 |
473 | this.DrainNode = function(node, successHandler, errorHandler){
474 | var url = getDrainURI() + node
475 | $.ajax({
476 | url: url,
477 | success: successHandler,
478 | error: errorHandler,
479 | timeout: this.timeout
480 |
481 | });
482 | if (this.debug){
483 | console.log("Called: ", url);
484 | }
485 | };
486 |
487 | this.UncordonNode = function(node, successHandler, errorHandler){
488 | var url = getUncordonURI() + node
489 | $.ajax({
490 | url: url,
491 | success: successHandler,
492 | error: errorHandler,
493 | timeout: this.timeout
494 |
495 | });
496 | if (this.debug){
497 | console.log("Called: ", url);
498 | }
499 | };
500 |
501 | this.Create = function(successHandler, errorHandler){
502 | var url = getCreateURI();
503 | $.ajax({
504 | url: url,
505 | success: successHandler,
506 | error: errorHandler,
507 | timeout: this.timeout
508 |
509 | });
510 | if (this.debug){
511 | console.log("Called: ", url);
512 | }
513 | };
514 |
515 | this.Get = function(successHandler, errorHandler){
516 | var url = getPodsURI();
517 | $.ajax({
518 | url: url,
519 | success: successHandler,
520 | error: errorHandler,
521 | timeout: this.timeout
522 |
523 | });
524 | if (this.debug){
525 | console.log("Called: ", url);
526 | }
527 | };
528 |
529 | this.GetNodes = function(successHandler, errorHandler){
530 | var url = getPodsURI();
531 | $.ajax({
532 | url: url,
533 | success: successHandler,
534 | error: errorHandler,
535 | timeout: this.timeout
536 |
537 | });
538 | if (this.debug){
539 | console.log("Called: ", url);
540 | }
541 | };
542 |
543 | this.ResetNodes = function(){
544 | this.GetNodes(handleRefreshNodes);
545 |
546 | };
547 |
548 | var handleRefreshNodes = function(nodes, ex){
549 | for (var i = 0; i < nodes.items.length; i++){
550 | var node = nodes.items[i];
551 | var url = getUncordonURI() + node.metadata.name;
552 | ajaxProxy(url);
553 | }
554 | };
555 |
556 |
557 | }
558 |
559 | function GAME(){
560 | var state = "new";
561 | var bombShowed= false;
562 | var serviceDown = true;
563 |
564 | this.gameInterval = "";
565 | this.scoreInterval = "";
566 | this.podsInterval = "";
567 | this.clockInterval = "";
568 |
569 | this.HasBombShowed = function(){
570 | return bombShowed;
571 | }
572 |
573 | this.SetBombShowed = function(){
574 | bombShowed = true;;
575 | }
576 |
577 |
578 | this.IsServiceDown = function(){
579 | return serviceDown;
580 | }
581 |
582 | this.SetServiceDown = function(){
583 | serviceDown = true;
584 | }
585 |
586 | this.SetServiceUp = function(){
587 | if (this.state != "done"){
588 | serviceDown = false;
589 | }
590 | }
591 |
592 |
593 | this.GetState = function(){
594 | return state;
595 | }
596 |
597 | this.Start = function(colorFunction, scoreFunction, podsFunction, clockFunction){
598 | this.gameInterval = setInterval(colorFunction, 300);
599 | this.scoreInterval = setInterval(scoreFunction, 10);
600 | this.podsInterval = setInterval(podsFunction, 100);
601 | this.clockInterval = setInterval(clockFunction, 100);
602 | state = "started";
603 | startTime = Date.now();
604 | }
605 |
606 | this.Init = function(){
607 | console.log("Init called.")
608 | state = "running";
609 | this.SetServiceUp();
610 | }
611 |
612 | this.Stop = function(){
613 | state = "done";
614 | window.clearInterval(this.gameInterval);
615 | window.clearInterval(this.scoreInterval);
616 | window.clearInterval(this.podsInterval);
617 | window.clearInterval(this.clockInterval);
618 | this.SetServiceDown();
619 | }
620 |
621 | }
622 |
623 | function SOUNDS(){
624 |
625 | var hit = "";
626 | var hit2 = "";
627 | var explosion = "";
628 | var countdown = "";
629 | var startup = "";
630 |
631 | var makeSource = function(file,volume,loop){
632 | if (typeof(loop)==='undefined') loop = false;
633 | if (typeof(volume)==='undefined') volume = 0.3;
634 |
635 | var fileExt = file.split('.').pop();
636 | var type = "audio/wav";
637 |
638 | if (fileExt == "mp3"){
639 | type = "audio/mpeg";
640 | }
641 |
642 | var result = new Audio();
643 | var src = document.createElement("source");
644 | src.type = type;
645 | src.src = file;
646 | result.preload = "auto";
647 | result.append(src);
648 | result.volume = volume;
649 | return result;
650 | };
651 |
652 | this.SetWhack = function(filename,volume){
653 | hit = makeSource(filename,volume);
654 | hit2 = makeSource(filename,volume);
655 | };
656 |
657 | this.SetExplosion = function(filename,volume){
658 | explosion = makeSource(filename,volume);
659 | };
660 |
661 | this.SetCountdown = function(filename,volume){
662 | countdown = makeSource(filename,volume);
663 | };
664 |
665 | this.SetStartup = function(filename,volume){
666 | startup = makeSource(filename,volume);
667 | };
668 |
669 | this.PlayWhack = function(filename,volume){
670 | if (!hit.paused){
671 | hit2.play();
672 | } else{
673 | hit.play();
674 | }
675 | };
676 |
677 | this.PlayExplosion = function(filename,volume){
678 | explosion.play();
679 | };
680 |
681 | this.PlayCountdown = function(filename,volume){
682 | countdown.play();
683 | };
684 |
685 | this.PlayStartup = function(filename,volume){
686 | startup.play();
687 | };
688 |
689 | }
690 |
691 | function CLOCK(d, handler){
692 | var start_time = new Date();
693 | var duration = d;
694 | var completeHandler = handler;
695 |
696 | var shutItDown = function(){
697 | completeHandler();
698 | window.clearInterval(watcher);
699 | };
700 |
701 | var checkComplete = function(){
702 | var diff = new Date() - Date.parse(start_time);
703 | var count = Math.floor(diff/1000);
704 | if (count > duration){
705 | shutItDown();
706 | }
707 | };
708 |
709 | this.getTimeLeft = function(){
710 | var diff = new Date() - Date.parse(start_time);
711 | var count = Math.floor(diff/1000);
712 | var result = duration - count;
713 | if (result == 0){
714 | shutItDown();
715 | }
716 | return result;
717 | };
718 | var watcher = setInterval(checkComplete, 200);
719 | }
720 |
721 | function BOMBUI(waitingimg, explodeimg){
722 | var waiting = waitingimg;
723 | var explode = explodeimg;
724 |
725 | this.Explode = function(){
726 | document.querySelector("#bomb").src = explode;
727 | setTimeout(this.Reset, 3000);
728 |
729 | };
730 |
731 | this.Show = function(){
732 | $(".bombpanel").show();
733 | };
734 |
735 | this.Reset = function(){
736 | document.querySelector("#bomb").src = waiting;
737 | $(".bombpanel").hide();
738 | var timeToCallShow = Math.random() * 500000;
739 | setTimeout(this.Show, timeToCallShow);
740 | };
741 |
742 | }
743 |
744 | function PODLIST(json){
745 | this.selflink = json.metadata.selfLink;
746 |
747 | this.items = [];
748 |
749 | for (var i=0; i< json.items.length; i++){
750 | var item = new POD(json.items[i]);
751 | this.items.push(item)
752 | }
753 |
754 | }
755 |
756 |
757 | function LOGWINDOW(){
758 | var alreadyShown = {};
759 | alreadyShown.terminating = {};
760 | alreadyShown.pending = {};
761 | alreadyShown.running = {};
762 |
763 |
764 | var IsAlreadyShown = function(pod){
765 | if (typeof(alreadyShown[pod.phase][pod.name])==='undefined'){
766 | return false;
767 | }
768 | return true;
769 | };
770 |
771 | var IsError = function(e){
772 | if (e.kind == "Status"){
773 | return true;
774 | }
775 | if (typeof e.error != "undefined"){
776 | return true;
777 | }
778 | return false;
779 | };
780 |
781 | this.Log = function(ev){
782 |
783 |
784 | var e = jQuery.extend(true, {}, ev);
785 | var item = e;
786 | if (e.kind == "Pod"){
787 | item = new POD(e);
788 | }
789 |
790 | if (e.kind == "Node"){
791 | item = new NODE(e);
792 | }
793 |
794 | if (e.kind == "PodList"){
795 | item = new PODLIST(e);
796 | }
797 |
798 | if (typeof e.metadata != "undefined"){
799 | if (e.metadata.selfLink.indexOf("Pod") > -1 ) {
800 | item = new POD(e);
801 | }
802 |
803 | if (e.metadata.selfLink.indexOf("Node") > -1 ) {
804 | item = new NODE(e);
805 | }
806 | }
807 |
808 | if (IsError(item)){
809 | return;
810 | }
811 |
812 |
813 |
814 | if (item.type === "Pod"){
815 | if (IsAlreadyShown(item)){
816 | return;
817 | }
818 |
819 | alreadyShown[item.phase][item.name] = "";
820 | delete item.terminateThreshold;
821 | delete item.holder;
822 | if (item.startTerminate == ""){
823 | delete item.startTerminate;
824 | }
825 | }
826 |
827 |
828 |
829 | var output = JSON.stringify(item,null,2);
830 | var textArray = output.split("\n");
831 |
832 | for (var i = textArray.length -1; i >= 0; i--){
833 | if (textArray[i].length < 3){
834 | continue;
835 | }
836 | var css_class = "";
837 | var content = '' + textArray[i] + '
';
838 | if (textArray[i].indexOf("phase") >= 0){
839 | css_class = "phase";
840 | content = '' + textArray[i] + '
';
841 | }
842 | $(content).prependTo("#logwindow").hide().delay( (textArray.length - i) * 50 ).slideDown();
843 | }
844 |
845 | var consoleLength = $("#logwindow div").length;
846 | if (consoleLength > 2000){
847 | var diff = -(consoleLength - 2000);
848 | $('#logwindow > div').slice(diff).remove();
849 | console.log("Log window trimmed");
850 | }
851 |
852 | };
853 | }
854 |
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/js/main.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. 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 | var default_duration = 40;
15 |
16 | // These are set in config.js, and are specific to your cluster setup
17 | var api = new API(servicehost);
18 | var deploymentAPI = new DEPLOYMENTAPI(adminhost);
19 | var pods = new PODS();
20 | var podsUI = new PODSUI(pods);
21 | var bombUI = new BOMBUI("assets/img/bomb_waiting.png", "assets/img/bomb_explode.png");
22 | var game = new GAME();
23 | var clock = "";
24 | var score = new SCORE();
25 | var sounds = new SOUNDS();
26 | sounds.SetWhack("assets/audio/pop.wav",.5);
27 | sounds.SetExplosion("assets/audio/explosion.wav",.5);
28 | sounds.SetCountdown("assets/audio/countdown.mp3",.5);
29 | sounds.SetStartup("assets/audio/startup.mp3",.5);
30 |
31 | document.addEventListener('DOMContentLoaded', function() {
32 | $("#start-modal").show();
33 | $(".timer").html(default_duration);
34 | setReport("Kubernetes service not started yet.");
35 | deploymentAPI.Delete();
36 | var interval = Math.random() * 200000;
37 | document.querySelector("#bomb").addEventListener("click", bombClickHandler);
38 | document.querySelector("#deploy-start").addEventListener("click", startDeployment);
39 | document.querySelector("#restart").addEventListener("click", restart);
40 | });
41 |
42 | function setReport(msg, color){
43 | if (typeof color == "undefined") color = "#333333";
44 | var report = document.querySelector(".report");
45 | report.innerHTML = "" + msg + " ";
46 | report.style.color = color;
47 | }
48 |
49 | function endDeployment(){
50 | deploymentAPI.Delete();
51 | game.Stop();
52 | showTotals();
53 | podsUI.ClearAll();
54 | setReport("Kubernetes service went away!");
55 | showModal("#end-modal");
56 | }
57 |
58 | function showTotals(){
59 | $("#total-pods").html(score.GetPods() + " pods");
60 | $("#total-knockdowns").html(score.GetKnockDowns() + " service disruptions");
61 | $("#total-score").html(score.GetTotal() + " points");
62 | }
63 |
64 | function startDeployment(){
65 | deploymentAPI.Create(initGame,genericError);
66 | hideModal("#start-modal");
67 | setReport("Kubernetes starting up.");
68 | }
69 |
70 | function initGame(){
71 | game.Start(getColor, showScore, getPods, getTimeLeft);
72 | }
73 |
74 | function getColor(){
75 | api.Color(handleColor, handleColorError)
76 | }
77 |
78 | function getTimeLeft(){
79 | if (clock != ""){
80 | $(".timer").html(clock.getTimeLeft());
81 | if (clock.getTimeLeft() <= 4){
82 | sounds.PlayCountdown();
83 | }
84 | }
85 | }
86 |
87 | function handleColor(e){
88 | if (game.GetState() == "started"){
89 | game.Init();
90 | clock = new CLOCK(default_duration, endDeployment);
91 | sounds.PlayStartup();
92 | }
93 |
94 | if (game.GetState() == "running"){
95 | game.SetServiceUp();
96 | setReport("Kubernetes service is UP!", e);
97 | }
98 |
99 | }
100 |
101 | function handleColorError(e,textStatus, errorThrown){
102 | if (game.GetState() == "running") {
103 | setReport("Kubernetes service is DOWN!", "#FF0000");
104 | alertYouKilledIt();
105 | }
106 | }
107 |
108 | function showScore(){
109 | document.querySelector(".scoreboard .total").innerHTML = score.GetTotal();
110 | if (score.GetTotal() >= 25 && !game.HasBombShowed()){
111 | bombUI.Show();
112 | game.SetBombShowed();
113 | }
114 | }
115 |
116 | function getPods(){
117 | deploymentAPI.Get(handlePods, handlePodsError);
118 | }
119 |
120 | function handlePods(e){
121 | if (game.GetState() == "done") {
122 | return;
123 | }
124 |
125 | podsUI.DrawPods(e, whackHandler);
126 | }
127 |
128 | function handlePodsError(e){
129 | $(".pods").html("");
130 | console.log("Error getting pods:", e);
131 | }
132 |
133 | function alertYouKilledIt(){
134 | if (!game.IsServiceDown() && game.GetState() == "running"){
135 | console.log("Killed it.");
136 | game.SetServiceDown();
137 | score.KnockDown()
138 | $(".alert .msg").html("You knocked down the service.");
139 | $(".alert").show();
140 | setTimeout(hideAlert, 3000);
141 | }
142 | }
143 |
144 | function whackHandler(e){
145 | sounds.PlayWhack();
146 | killPod(e.target.dataset.selflink)
147 | }
148 |
149 | function killPod(selflink){
150 | deploymentAPI.DeletePod(selflink, score.KillPod, podError);
151 | }
152 |
153 | function bombClickHandler(e){
154 | deploymentAPI.Get(bombBlastHandler, genericError);
155 | }
156 |
157 | function bombBlastHandler(e){
158 | sounds.PlayExplosion();
159 | for (var i = 0; i < e.items.length; i++){
160 | var pod = e.items[i];
161 | if (pod.status.phase == "Running"){
162 | killPod(pod.metadata.selfLink);
163 | }
164 | }
165 | bombUI.Explode();
166 | }
167 |
168 | // Add functions below to lib.js
169 | function genericError(e){
170 | console.log("Error: ", e);
171 | }
172 |
173 | function hideModal(id){
174 | var modal = $(id);
175 | modal.fadeOut();
176 | }
177 |
178 | function podError(e){
179 | console.log("Pod already gone? :", e);
180 | }
181 |
182 | function showModal(id){
183 | var modal = $(id);
184 | modal.fadeIn('slow');
185 | }
186 |
187 | function hideAlert(){
188 | $(".alert").fadeOut('slow');
189 | }
--------------------------------------------------------------------------------
/apps/game/containers/default/assets/js/next.js:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. 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 | var default_duration = 40;
15 |
16 | // These are set in config.js, and are specific to your cluster setup
17 | var api = new API(servicehost);
18 | var logwindow = new LOGWINDOW();
19 | var deploymentAPI = new DEPLOYMENTAPI(adminhost, logwindow);
20 | var pods = new PODS();
21 | var podsUI = new PODSUI(pods, logwindow);
22 | var bombUI = new BOMBUI("assets/img/bomb_waiting_next.png", "assets/img/bomb_explode_next.png");
23 | var game = new GAME();
24 | var clock = "";
25 | var score = new SCORE();
26 | var sounds = new SOUNDS();
27 | var fails_threshold = 9;
28 | sounds.SetWhack("assets/audio/pop.wav",.5);
29 | sounds.SetExplosion("assets/audio/explosion.wav",.5);
30 | sounds.SetCountdown("assets/audio/countdown.mp3",.5);
31 | sounds.SetStartup("assets/audio/startup.mp3",.5);
32 |
33 |
34 | document.addEventListener('DOMContentLoaded', function() {
35 | $("#start-modal").show();
36 | $(".timer").html(default_duration);
37 | setReport("");
38 | deploymentAPI.Delete();
39 | var interval = Math.random() * 200000;
40 | $("#bomb").click(bombClickHandler);
41 | $("#deploy-start").click(startDeployment);
42 | $("#restart").click(restart);
43 | $("#deploy-start").focus();
44 | });
45 |
46 |
47 | function setReport(msg, color){
48 | if (typeof color == "undefined") color = "#333333";
49 | var report = $(".report");
50 |
51 | report.css("-webkit-filter", "drop-shadow(2px 2px 3px " + color + ")");
52 | report.css("color", color);
53 | var msgholder = $("#report_message");
54 | if (msgholder.length == 0){
55 | report.after('' + msg + '
')
56 | } else {
57 | msgholder.html(msg);
58 | }
59 |
60 |
61 | if(game.GetState() == "running"){
62 | if(game.IsServiceDown()) {
63 | $(".report").addClass("service_down");
64 | $(".report").removeClass("service_up");
65 | } else{
66 | $(".report").addClass("service_up");
67 | $(".report").removeClass("service_down");
68 | }
69 | }
70 |
71 | }
72 |
73 | function endDeployment(){
74 | deploymentAPI.Delete();
75 | game.Stop();
76 | showTotals();
77 | podsUI.ClearAll();
78 | setReport("");
79 | $(".report").removeClass("service_up");
80 | $(".report").removeClass("service_down");
81 | showModal("#end-modal");
82 | setTimeout(function(){location.reload();}, 15000);
83 | }
84 |
85 | function showTotals(){
86 | $("#total-pods").html(score.GetPods() + " pods");
87 | $("#total-knockdowns").html(score.GetKnockDowns() + " service disruptions");
88 | $("#total-score").html(score.GetTotal() + " points");
89 | }
90 |
91 | function startDeployment(){
92 | deploymentAPI.Create(initGame,genericError);
93 | hideModal("#start-modal");
94 | $(".pods-explain").fadeIn();
95 |
96 | setReport("");
97 | }
98 |
99 | function initGame(e){
100 | game.Start(getColor, showScore, getPods, getTimeLeft);
101 | logwindow.Log(e);
102 |
103 | }
104 |
105 | function getColor(){
106 | api.Color(handleColor, handleColorError)
107 | }
108 |
109 | function getTimeLeft(){
110 | if (clock != ""){
111 | $(".timer").html(clock.getTimeLeft());
112 | if (clock.getTimeLeft() <= 4){
113 | sounds.PlayCountdown();
114 | }
115 | }
116 | }
117 |
118 | function handleColor(e){
119 | if (game.GetState() == "started"){
120 | game.Init();
121 | clock = new CLOCK(default_duration, endDeployment);
122 | sounds.PlayStartup();
123 | setTimeout(function(){$(".pods-explain").fadeOut();}, 3000);
124 | }
125 |
126 | if (game.GetState() == "running"){
127 | game.SetServiceUp();
128 | setReport("", e);
129 | }
130 | if (api.fails > 0){
131 | api.fails = 0;
132 | console.log("Soft service has recovered.");
133 | }
134 |
135 |
136 | }
137 |
138 | function handleColorError(e,textStatus, errorThrown){
139 | if (game.GetState() == "running") {
140 | if (api.fails > fails_threshold){
141 | console.log("Hard service fail.");
142 | setReport("Kubernetes service is DOWN!", "#FF0000");
143 | alertYouKilledIt();
144 | } else {
145 | console.log("Soft service fail. Retry");
146 | api.fails++;
147 | }
148 |
149 | }
150 |
151 | }
152 |
153 | function showScore(){
154 | document.querySelector(".scoreboard .total").innerHTML = score.GetTotal();
155 | if (score.GetTotal() >= 25 && !game.HasBombShowed()){
156 | bombUI.Show();
157 | game.SetBombShowed();
158 | }
159 | }
160 |
161 | function getPods(){
162 | deploymentAPI.Get(handlePods, handlePodsError);
163 | }
164 |
165 | function handlePods(e){
166 | if (game.GetState() == "done") {
167 | return;
168 | }
169 |
170 | podsUI.DrawPods(e, whackHandler);
171 | }
172 |
173 | function handlePodsError(e){
174 | $(".pods").html("");
175 | console.log("Error getting pods:", e);
176 | }
177 |
178 | function alertYouKilledIt(){
179 | if (!game.IsServiceDown() && game.GetState() == "running"){
180 | console.log("Killed it.");
181 | game.SetServiceDown();
182 | score.KnockDown()
183 | $(".alert .msg").html("You knocked down the service.");
184 | $(".alert").show();
185 | setTimeout(hideAlert, 3000);
186 | }
187 | }
188 |
189 | function whackHandler(e){
190 | sounds.PlayWhack();
191 | if (e.target.id == ""){
192 | $("#" + e.target.parentNode.id ).addClass("terminating");
193 | } else{
194 | $("#" + e.target.id ).addClass("terminating");
195 | }
196 |
197 |
198 | killPod(e.target.dataset.selflink)
199 | }
200 |
201 | function killPod(selflink){
202 | deploymentAPI.DeletePod(selflink, killHandler, podError);
203 | }
204 |
205 | function killHandler(e){
206 | score.KillPod();
207 | logwindow.Log(e);
208 | }
209 |
210 | function bombClickHandler(e){
211 | deploymentAPI.Get(bombBlastHandler, genericError);
212 | }
213 |
214 | function bombBlastHandler(e){
215 | sounds.PlayExplosion();
216 | for (var i = 0; i < e.items.length; i++){
217 | var pod = e.items[i];
218 | if (pod.status.phase == "Running"){
219 | killPod(pod.metadata.selfLink);
220 | }
221 | }
222 | bombUI.Explode();
223 | }
224 |
225 |
226 | function showModal(id){
227 | var modal = $(id);
228 | modal.fadeIn('slow');
229 | }
230 |
231 | function hideModal(id){
232 | var modal = $(id);
233 | modal.fadeOut();
234 | }
235 |
236 | function restart(){
237 | location.reload();
238 | }
239 |
240 | function hideAlert(){
241 | $(".alert").fadeOut('slow');
242 | }
243 |
244 | function podError(e){
245 | console.log("Pod already gone? :", e);
246 | }
247 |
248 | function genericError(e){
249 | console.log("Error: ", e);
250 | }
--------------------------------------------------------------------------------
/apps/game/containers/default/healthz/index.html:
--------------------------------------------------------------------------------
1 | ok
--------------------------------------------------------------------------------
/apps/game/containers/default/index.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | Whack a Pod!
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
You will see random flashes of green colors while the service is up.
32 | Manage to kill the service and this box goes red.
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
Click the bomb to whack all the pods at once!
64 |
65 |
66 |
67 |
68 | Time:
69 | 0
70 |
71 |
72 | Score:
73 | 0
74 |
75 |
76 |
77 |
83 |
84 |
Each one of these moles represents an actual Kuberntes pod. When
85 | you whack them, you kill the pod. The goal is to try and knock down
86 | enough pods that you disrupt the service. Notice:
87 |
88 |
89 | How fast Kubernetes pods come back up
90 | How hard it is to disrupt the service.
91 | How fast the service comes back up after you manage to disrupt it
92 |
93 |
94 |
95 |
Scoring
96 |
97 | Knock down pod: 1 point
98 | Disrupt service: 100 points
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
Welcome!
107 |
Push the button to deploy to Kubernetes.
108 | When ready, a whistle wil blow, and then you'll get to knock down
109 | Kubernetes pods/moles. The pods are keeping your
110 | service up.
111 |
112 |
See if you can knock down enough of them to
113 | disrupt the service.
114 |
115 |
Click to deploy
116 |
117 |
118 | kubectl create -f whack-a-pod-deployment.yaml
119 |
120 |
121 |
122 |
123 |
124 |
125 |
Good Job!
126 |
You did the following:
127 |
128 | Killed 0
129 | Caused 0
130 | For total of 0
131 |
132 |
Restart
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/apps/game/containers/default/next.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | Whack a Pod!
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | Can't hit… yet.
59 |
60 |
61 | Hit away.
62 |
63 |
64 | Already Hit.
65 |
66 |
67 |
68 |
71 |
72 |
73 |
Click the star to whack all the pods at once!
74 |
75 |
76 |
77 |
78 | Time:
79 | 0
80 |
81 |
82 | Score:
83 | 0
84 |
85 |
86 |
87 |
93 |
94 |
Each one of these moles represents an actual Kuberntes pod. When
95 | you whack them, you kill the pod. The goal is to try and knock down
96 | enough pods that you disrupt the service. Notice:
97 |
98 |
99 | How fast Kubernetes pods come back up
100 | How hard it is to disrupt the service.
101 | How fast the service comes back up after you manage to disrupt it
102 |
103 |
104 |
105 |
Scoring
106 |
107 | Knock down pod: 1 point
108 | Disrupt service: 100 points
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
Welcome!
117 |
Push the button to deploy to Kubernetes.
118 | When ready, a whistle will blow, and then you'll get to knock down
119 | Kubernetes pods. The pods are keeping your service up.
120 |
121 |
See if you can knock down enough of them to
122 | disrupt the service!
123 |
124 |
Click to deploy
125 |
126 |
127 | kubectl create -f whack-a-pod-deployment.yaml
128 |
129 |
130 |
131 |
132 |
133 |
134 |
Good Job!
135 |
You did the following:
136 |
137 | Killed 0
138 | Caused 0
139 | For total of 0
140 |
141 |
Restart
142 |
143 |
144 |
145 |
146 | Advanced View
147 |
148 |
--------------------------------------------------------------------------------
/apps/game/docker/Makefile:
--------------------------------------------------------------------------------
1 | BASEDIR = $(shell pwd)
2 |
3 | .DEFAULT_GOAL := app
4 |
5 | app: clean build serve
6 |
7 |
8 |
9 | build:
10 | docker build -t game "$(BASEDIR)/../containers/."
11 |
12 | serve:
13 | docker run --name=game -d -P -p 8080:8080 game
14 |
15 | clean:
16 | -docker stop game
17 | -docker rm game
18 | -docker rmi game
--------------------------------------------------------------------------------
/apps/game/kubernetes/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | BASEDIR = $(shell pwd)
15 |
16 | include ../../../Makefile.properties
17 |
18 | app: build deploy
19 |
20 | reset: clean.deployment app
21 | say "app refresh complete"
22 |
23 |
24 | reset.safe: env creds
25 | gcloud container builds submit "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO)/game
26 | -kubectl delete deployment game-deployment
27 | kubectl run game-deployment --image=$(DOCKERREPO)/game --replicas=4 --port=8080 --labels=app=game
28 | say "app refresh complete"
29 |
30 | build: env
31 | gcloud container builds submit "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO)/game
32 |
33 | build.dockerhub:
34 | docker build "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO):game
35 | docker push $(DOCKERREPO):game
36 |
37 | build.generic:
38 | docker build "$(BASEDIR)/../containers/." --tag=$(DOCKERREPO)/whackapod-game
39 | docker push $(DOCKERREPO)/whackapod-game
40 |
41 | deploy: env creds deployment service
42 |
43 | deploy.minikube: deployment service
44 |
45 | deploy.minikube.dockerhub:
46 | kubectl run game-deployment --image=$(DOCKERREPO):game --replicas=4 --port=8080 --labels=app=game
47 | kubectl expose deployment game-deployment --name=game --target-port=8080 --type=NodePort --labels="app=game"
48 |
49 | deploy.generic:
50 | kubectl run game-deployment --image=$(DOCKERREPO)/whackapod-game --replicas=4 --port=8080 --labels=app=game
51 | kubectl expose deployment game-deployment --name=game --target-port=8080 --type=NodePort --labels="app=game"
52 |
53 | deployment:
54 | kubectl run game-deployment --image=$(DOCKERREPO)/game --replicas=4 --port=8080 --labels=app=game
55 |
56 | service:
57 | kubectl expose deployment game-deployment --name=game --target-port=8080 --type=NodePort --labels="app=game"
58 |
59 | clean: env creds clean.deployment clean.service
60 |
61 | clean.generic: clean.deployment clean.service
62 |
63 | clean.minikube: clean.deployment clean.service
64 |
65 | clean.deployment:
66 | -kubectl delete deployment game-deployment
67 |
68 | clean.service:
69 | -kubectl delete service game
70 |
71 |
72 | retry: clean build deploy
73 |
74 | config: env
75 |
--------------------------------------------------------------------------------
/apps/ingress/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | BASEDIR = $(shell pwd)
15 |
16 | include ../../Makefile.properties
17 |
18 | deploy: env creds
19 | kubectl apply -f "$(BASEDIR)/ingress.yaml"
20 |
21 | deploy.minikube:
22 | kubectl apply -f "$(BASEDIR)/ingress.minikube.yaml"
23 |
24 | deploy.generic:
25 | kubectl apply -f "$(BASEDIR)/ingress.generic.yaml"
26 |
27 | config: env
28 | @echo Creating INGRESS Yaml files based on samples and setting in your Makefile.properties
29 | @cp "$(BASEDIR)/ingress.sample.yaml" "$(BASEDIR)/ingress.yaml"
30 | $(call rewritefile,"$(BASEDIR)/ingress.yaml",%INGRESSNAME%,$(INGRESSNAME))
31 |
32 | clean: env creds
33 | -kubectl delete -f "$(BASEDIR)/ingress.yaml"
34 |
35 | clean.minikube:
36 | -kubectl delete -f "$(BASEDIR)/ingress.minikube.yaml"
37 |
38 | clean.generic:
39 | -kubectl delete -f "$(BASEDIR)/ingress.generic.yaml"
40 |
41 | define rewritefile
42 | @sed s/$(2)/$(3)/g <""$(1)"" >"$(BASEDIR)/.temp"
43 | @cp "$(BASEDIR)/.temp" $(1)
44 | @rm "$(BASEDIR)/.temp"
45 | endef
46 |
--------------------------------------------------------------------------------
/apps/ingress/ingress.generic.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | apiVersion: extensions/v1beta1
15 | kind: Ingress
16 | metadata:
17 | name: wap-ingress
18 | annotations:
19 | nginx.ingress.kubernetes.io/rewrite-target: /
20 | spec:
21 | rules:
22 | - http:
23 | paths:
24 | - path: /api/*
25 | backend:
26 | serviceName: api
27 | servicePort: 8080
28 | - path: /admin/*
29 | backend:
30 | serviceName: admin
31 | servicePort: 8080
32 | - path: /*
33 | backend:
34 | serviceName: game
35 | servicePort: 8080
36 | host: whackapod.example.com
37 |
--------------------------------------------------------------------------------
/apps/ingress/ingress.minikube.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | apiVersion: extensions/v1beta1
15 | kind: Ingress
16 | metadata:
17 | name: wap-ingress
18 | annotations:
19 | ingress.kubernetes.io/rewrite-target: /
20 | spec:
21 | rules:
22 | - host: minikube.wap
23 | http:
24 | paths:
25 | - path: /api/
26 | backend:
27 | serviceName: api
28 | servicePort: 8080
29 | - path: /admin/
30 | backend:
31 | serviceName: admin
32 | servicePort: 8080
33 | - path: /
34 | backend:
35 | serviceName: game
36 | servicePort: 8080
37 |
--------------------------------------------------------------------------------
/apps/ingress/ingress.sample.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | apiVersion: extensions/v1beta1
15 | kind: Ingress
16 | metadata:
17 | name: wap-ingress
18 | annotations:
19 | kubernetes.io/ingress.global-static-ip-name: %INGRESSNAME%
20 | spec:
21 | rules:
22 | - http:
23 | paths:
24 | - path: /api/*
25 | backend:
26 | serviceName: api
27 | servicePort: 8080
28 | - path: /admin/*
29 | backend:
30 | serviceName: admin
31 | servicePort: 8080
32 | - path: /*
33 | backend:
34 | serviceName: game
35 | servicePort: 8080
--------------------------------------------------------------------------------
/infrastructure/Makefile:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Google Inc. 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 | BASEDIR = $(shell pwd)
15 |
16 | include ../Makefile.properties
17 | NODEDISKSIZE="200"
18 | NODECOUNT="2"
19 | NODETYPE="n1-standard-4"
20 |
21 | # Builds the entire infrastructure you need to run this set of demos.
22 | build: env build.cluster ip
23 |
24 | # Requests a GKE cluster
25 | build.cluster:
26 | -gcloud container clusters create $(CLUSTER) --num-nodes $(NODECOUNT) \
27 | --disk-size=$(NODEDISKSIZE) --machine-type=$(NODETYPE) --zone=$(ZONE)
28 |
29 | # Runs all the clean commands.
30 | clean: env clean.cluster clean.repo clean.ip
31 |
32 | # deletes the cluster.
33 | clean.cluster:
34 | gcloud container clusters delete $(CLUSTER) -q
35 |
36 | # Gets rid of all of the images in GKR
37 | clean.repo:
38 | -gsutil -m rm -r gs://artifacts.$(PROJECT).appspot.com
39 |
40 | ip:
41 | gcloud compute --project $(PROJECT) addresses create $(INGRESSNAME) --global
42 |
43 | clean.ip:
44 | gcloud compute --project $(PROJECT) addresses delete $(INGRESSNAME)
45 |
46 | minikube:
47 | minikube start
48 |
49 | clean.minikube:
50 | minikube start
--------------------------------------------------------------------------------
/screenshots/advanced.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/screenshots/advanced.png
--------------------------------------------------------------------------------
/screenshots/game.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/screenshots/game.png
--------------------------------------------------------------------------------
/screenshots/next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tpryan/whack_a_pod/c2053a1917e70a0c28f478d183beb58e8697af78/screenshots/next.png
--------------------------------------------------------------------------------