├── applications ├── puzzle │ ├── .eslintignore │ ├── .dockerignore │ ├── .eslintrc │ ├── .yo-rc.json │ ├── up.sh │ ├── down.sh │ ├── server │ │ ├── component-config.json │ │ ├── boot │ │ │ ├── authentication.js │ │ │ ├── root.js │ │ │ ├── create-sample-models.js │ │ │ └── puzzle.json │ │ ├── middleware.development.json │ │ ├── datasources.json │ │ ├── config.json │ │ ├── server.js │ │ ├── middleware.json │ │ └── model-config.json │ ├── docker-compose.yml │ ├── .gitignore │ ├── .editorconfig │ ├── Dockerfile │ ├── README.md │ ├── Jenkinsfile │ ├── package.json │ ├── k8s │ │ └── deployment.yaml │ └── common │ │ └── models │ │ ├── crossword.json │ │ └── crossword.js ├── monitor-scale │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── Jenkinsfile │ ├── package.json │ ├── k8s │ │ └── deployment.yaml │ └── index.js ├── kr8sswordz-pages │ ├── .dockerignore │ ├── src │ │ ├── styles │ │ │ ├── templates │ │ │ │ ├── _templates.scss │ │ │ │ └── _home-page.scss │ │ │ ├── atoms │ │ │ │ ├── _atoms.scss │ │ │ │ ├── _db-image.scss │ │ │ │ ├── _buttons.scss │ │ │ │ └── _slider.scss │ │ │ ├── organisms │ │ │ │ ├── _puzzle-container.scss │ │ │ │ ├── _organisms.scss │ │ │ │ ├── instance-grid.scss │ │ │ │ └── _header.scss │ │ │ ├── molecules │ │ │ │ ├── _superscript-number.scss │ │ │ │ ├── _molecules.scss │ │ │ │ ├── _button-row.scss │ │ │ │ ├── _image-column.scss │ │ │ │ ├── _slider-container.scss │ │ │ │ ├── _data-flow-arrow.scss │ │ │ │ ├── _cell.scss │ │ │ │ ├── _puzzle.scss │ │ │ │ ├── _instance-box.scss │ │ │ │ └── _loader.scss │ │ │ └── utils │ │ │ │ ├── _utils.scss │ │ │ │ ├── _variables.scss │ │ │ │ ├── _typography.scss │ │ │ │ └── _normalize.scss │ │ ├── constants.js │ │ ├── assets │ │ │ ├── arrow.png │ │ │ ├── etcd.png │ │ │ ├── mongo.png │ │ │ ├── arrow-blue.png │ │ │ ├── etcd-logo.png │ │ │ ├── logo-reg-2x.png │ │ │ ├── MongoDB-logo.png │ │ │ ├── arrow-reverse.png │ │ │ ├── arrow-blue-reverse.png │ │ │ ├── fonts │ │ │ │ ├── Roboto-Bold-webfont.eot │ │ │ │ ├── Roboto-Bold-webfont.ttf │ │ │ │ ├── Roboto-Bold-webfont.woff │ │ │ │ ├── Roboto-Light-webfont.eot │ │ │ │ ├── Roboto-Light-webfont.ttf │ │ │ │ ├── Roboto-Italic-webfont.eot │ │ │ │ ├── Roboto-Italic-webfont.ttf │ │ │ │ ├── Roboto-Italic-webfont.woff │ │ │ │ ├── Roboto-Light-webfont.woff │ │ │ │ ├── Roboto-Regular-webfont.eot │ │ │ │ ├── Roboto-Regular-webfont.ttf │ │ │ │ ├── Roboto-BoldItalic-webfont.eot │ │ │ │ ├── Roboto-BoldItalic-webfont.ttf │ │ │ │ ├── Roboto-Regular-webfont.woff │ │ │ │ ├── Roboto-BoldItalic-webfont.woff │ │ │ │ ├── Roboto-LightItalic-webfont.eot │ │ │ │ ├── Roboto-LightItalic-webfont.ttf │ │ │ │ └── Roboto-LightItalic-webfont.woff │ │ │ ├── arrow.svg │ │ │ └── database.svg │ │ ├── components │ │ │ ├── home │ │ │ │ ├── instanceConfig.js │ │ │ │ ├── Database.js │ │ │ │ ├── DataFlowArrow.js │ │ │ │ ├── HomePage.js │ │ │ │ ├── InstancesComponent.js │ │ │ │ └── PuzzleComponent.js │ │ │ ├── shared │ │ │ │ ├── Loader.js │ │ │ │ ├── Header.js │ │ │ │ ├── Cell.js │ │ │ │ └── Slider.js │ │ │ ├── App.js │ │ │ └── instance-grid │ │ │ │ └── InstanceGrid.js │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── puzzleReducer.js │ │ │ └── websocketReducer.js │ │ ├── routes.js │ │ ├── store │ │ │ └── configureStore.js │ │ ├── index.html │ │ ├── index.js │ │ ├── actions │ │ │ ├── actionTypes.js │ │ │ ├── webSocketActions.js │ │ │ └── puzzleActions.js │ │ └── style.scss │ ├── .gitignore │ ├── favicon.ico │ ├── favicon.png │ ├── Dockerfile │ ├── .eslintrc │ ├── test │ │ └── helpers │ │ │ └── setup-browser-env.js │ ├── README.md │ ├── Jenkinsfile │ ├── webpack.config.js │ ├── styleguide-config.js │ ├── k8s │ │ └── deployment.yaml │ ├── gulpfile.js │ └── package.json ├── socat │ ├── entrypoint.sh │ └── Dockerfile ├── hello-kenzan │ ├── DockerFileEx.jpg │ ├── Dockerfile │ ├── index.html │ ├── Jenkinsfile │ └── k8s │ │ ├── deployment.yaml │ │ └── manual-deployment.yaml ├── spinnaker-helm │ ├── spinnaker-chart │ │ ├── Chart.yaml │ │ ├── static │ │ │ ├── kube.config │ │ │ ├── front50.yml │ │ │ ├── orca.yml │ │ │ ├── igor.yml │ │ │ ├── run-apache2.sh │ │ │ ├── echo.yml │ │ │ ├── clouddriver.yml │ │ │ ├── settings-old.js │ │ │ ├── settings.js │ │ │ └── gate.yml │ │ ├── templates │ │ │ ├── config.yml │ │ │ ├── NOTES.txt │ │ │ ├── _helpers.tpl │ │ │ ├── redis.yaml │ │ │ ├── echo.yml │ │ │ ├── gate.yml │ │ │ ├── igor.yml │ │ │ ├── orca.yml │ │ │ ├── front50.yaml │ │ │ ├── deck.yml │ │ │ └── clouddriver.yml │ │ ├── .helmignore │ │ └── values.yaml │ ├── start.sh │ └── README.md └── jenkins │ ├── Dockerfile │ └── plugins.txt ├── .gitignore ├── manifests ├── etcd-service.yaml ├── etcd-cluster.yaml ├── all-services.yaml ├── monitor-scale-serviceaccount.yaml ├── registry.yaml └── jenkins.yaml ├── scripts ├── etcd.sh ├── puzzle.sh ├── kr8sswordz-pages.sh ├── etcd-crd.sh ├── monitor-scale.sh └── kubescale.sh ├── Jenkinsfile ├── package.json ├── start.js ├── readme.js ├── part4.yml ├── part1.yml ├── part2.yml ├── part3.yml └── LICENSE.md /applications/puzzle/.eslintignore: -------------------------------------------------------------------------------- 1 | /client/ -------------------------------------------------------------------------------- /applications/monitor-scale/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /applications/puzzle/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /applications/puzzle/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "loopback" 3 | } -------------------------------------------------------------------------------- /applications/puzzle/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-loopback": {} 3 | } -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | *.log 4 | dist -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/templates/_templates.scss: -------------------------------------------------------------------------------- 1 | @import './home-page'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | .idea 4 | node_modules 5 | node_modules/ 6 | node_modules/* -------------------------------------------------------------------------------- /applications/socat/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | socat TCP4-LISTEN:5000,fork,reuseaddr TCP4:$REG_IP:$REG_PORT 4 | -------------------------------------------------------------------------------- /applications/puzzle/up.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | HOSTNAME=`hostname` 3 | curl "http://monitor-scale:3001/up/$HOSTNAME" 4 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.log 4 | db.json 5 | .idea/ 6 | .idea 7 | /styleguide 8 | -------------------------------------------------------------------------------- /applications/puzzle/down.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | HOSTNAME=`hostname` 3 | curl "http://monitor-scale:3001/down/$HOSTNAME" 4 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/atoms/_atoms.scss: -------------------------------------------------------------------------------- 1 | @import './buttons'; 2 | @import './slider'; 3 | @import './db-image'; 4 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/favicon.ico -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/favicon.png -------------------------------------------------------------------------------- /applications/puzzle/server/component-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "loopback-component-explorer": { 3 | "mountPath": "/explorer" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /applications/hello-kenzan/DockerFileEx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/hello-kenzan/DockerFileEx.jpg -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/constants.js: -------------------------------------------------------------------------------- 1 | const constants = { 2 | minikubeIp: '192.168.56.100' 3 | }; 4 | 5 | export default constants; 6 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: Spinnaker Helm Chart 3 | name: spinnaker 4 | version: 0.1.0 5 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/organisms/_puzzle-container.scss: -------------------------------------------------------------------------------- 1 | .puzzle-container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/arrow.png -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/etcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/etcd.png -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/mongo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/mongo.png -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/arrow-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/arrow-blue.png -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/etcd-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/etcd-logo.png -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/logo-reg-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/logo-reg-2x.png -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/MongoDB-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/MongoDB-logo.png -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/arrow-reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/arrow-reverse.png -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/arrow-blue-reverse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/arrow-blue-reverse.png -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/home/instanceConfig.js: -------------------------------------------------------------------------------- 1 | export const instanceConfig = { 2 | min: 1, 3 | max: 16, 4 | step: 1, 5 | defaultValue: 1, 6 | value: 1 7 | }; 8 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/organisms/_organisms.scss: -------------------------------------------------------------------------------- 1 | // Atoms 2 | // 3 | // Styleguide 2.0.0 4 | 5 | @import './header', 6 | './instance-grid', 7 | './puzzle-container'; 8 | -------------------------------------------------------------------------------- /applications/hello-kenzan/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | 3 | COPY index.html /usr/share/nginx/html/index.html 4 | COPY DockerFileEx.jpg /usr/share/nginx/html/DockerFileEx.jpg 5 | 6 | EXPOSE 80 -------------------------------------------------------------------------------- /applications/puzzle/server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function enableAuthentication(server) { 4 | // enable authentication 5 | server.enableAuth(); 6 | }; 7 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Bold-webfont.eot -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Bold-webfont.ttf -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Bold-webfont.woff -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Light-webfont.eot -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Light-webfont.ttf -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Italic-webfont.eot -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Italic-webfont.ttf -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Italic-webfont.woff -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Light-webfont.woff -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Regular-webfont.eot -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Regular-webfont.ttf -------------------------------------------------------------------------------- /applications/puzzle/docker-compose.yml: -------------------------------------------------------------------------------- 1 | mongo: 2 | image: mongo 3 | ports: 4 | - "27017:27017" 5 | web: 6 | build: . 7 | ports: 8 | - "3000:3000" 9 | links: 10 | - mongo 11 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-BoldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-BoldItalic-webfont.ttf -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-Regular-webfont.woff -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-LightItalic-webfont.eot -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-LightItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-LightItalic-webfont.ttf -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/fonts/Roboto-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chrijack/kubernetes-ci-cd/HEAD/applications/kr8sswordz-pages/src/assets/fonts/Roboto-LightItalic-webfont.woff -------------------------------------------------------------------------------- /applications/socat/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | #USER root 3 | #Installing socat 4 | RUN apk update && apk upgrade && apk add bash socat 5 | COPY entrypoint.sh entrypoint.sh 6 | ENTRYPOINT ["sh", "./entrypoint.sh"] -------------------------------------------------------------------------------- /applications/puzzle/server/middleware.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "final:after": { 3 | "strong-error-handler": { 4 | "params": { 5 | "debug": true, 6 | "log": true 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/start.sh: -------------------------------------------------------------------------------- 1 | sudo virsh net-start default 2 | 3 | minikube delete 4 | 5 | rm -rf ~/.minikube 6 | 7 | minikube start --vm-driver kvm --memory 8000 --cpus 4 --disk-size 40g 8 | 9 | 10 | 11 | 12 | helm install ./chart -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_superscript-number.scss: -------------------------------------------------------------------------------- 1 | 2 | .superscript-number { 3 | z-index: 1; 4 | position: absolute; 5 | top: 0.1rem; 6 | left: 0.1rem; 7 | font-size: 0.7rem; 8 | line-height: 0.7rem; 9 | } 10 | -------------------------------------------------------------------------------- /applications/puzzle/.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.dat 3 | *.iml 4 | *.log 5 | *.out 6 | *.pid 7 | *.seed 8 | *.sublime-* 9 | *.swo 10 | *.swp 11 | *.tgz 12 | *.xml 13 | .DS_Store 14 | .idea 15 | .project 16 | .strong-pm 17 | coverage 18 | node_modules 19 | npm-debug.log 20 | -------------------------------------------------------------------------------- /applications/puzzle/server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "host": "mongo", 4 | "port": 27017, 5 | "database": "puzzle", 6 | "username": "puzzle", 7 | "password": "", 8 | "name": "db", 9 | "connector": "mongodb" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /applications/puzzle/server/boot/root.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(server) { 4 | // Install a `/` route that returns server status 5 | var router = server.loopback.Router(); 6 | router.get('/', server.loopback.status()); 7 | server.use(router); 8 | }; 9 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/utils/_utils.scss: -------------------------------------------------------------------------------- 1 | // Settings 2 | // 3 | // Baseline settings 4 | // 5 | // Styleguide 1.0.0 6 | 7 | // styleguide:ignore:start 8 | @import './normalize', 9 | './variables', 10 | './typography'; 11 | // styleguide:ignore:end 12 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/shared/Loader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Loader (props) { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | } 10 | 11 | export default Loader; 12 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import puzzle from './puzzleReducer'; 3 | import webSocket from './websocketReducer'; 4 | 5 | const rootReducer = combineReducers({ 6 | puzzle, 7 | webSocket 8 | }); 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_molecules.scss: -------------------------------------------------------------------------------- 1 | @import './cell'; 2 | @import './superscript-number'; 3 | @import './button-row'; 4 | @import './instance-box'; 5 | @import './data-flow-arrow'; 6 | @import './image-column'; 7 | @import './puzzle'; 8 | @import './slider-container'; 9 | @import './loader'; 10 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_button-row.scss: -------------------------------------------------------------------------------- 1 | .button-row { 2 | padding: 1rem 0; 3 | display: flex; 4 | justify-content: space-between; 5 | align-items: center; 6 | 7 | &.center { 8 | justify-content: center; 9 | 10 | button, .button { 11 | margin: 0 0.5rem; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /manifests/etcd-service.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: example-etcd-cluster-client-service 5 | spec: 6 | selector: 7 | etcd_cluster: example-etcd-cluster 8 | app: etcd 9 | ports: 10 | - protocol: TCP 11 | port: 2379 12 | targetPort: 2379 13 | nodePort: 32379 14 | type: NodePort -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/atoms/_db-image.scss: -------------------------------------------------------------------------------- 1 | .db-image { 2 | position: relative; 3 | display: flex; 4 | flex-direction: row; 5 | align-items: center; 6 | justify-content: flex-start; 7 | padding: 3rem 0; 8 | 9 | img.db { 10 | width: 6rem; 11 | } 12 | img.logo { 13 | height: 3.5rem; 14 | padding: 0 2rem; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /applications/puzzle/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/kube.config: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | clusters: 3 | - cluster: 4 | insecure-skip-tls-verify: true 5 | server: http://localhost:8001 6 | name: minikube 7 | contexts: 8 | - context: 9 | cluster: minikube 10 | name: minikube 11 | current-context: minikube 12 | kind: Config 13 | preferences: {} 14 | users: 15 | - name: minikube -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/config.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: spinnaker-config 5 | data: 6 | {{ (.Files.Glob "static/*").AsConfig | indent 2 }} 7 | --- 8 | apiVersion: v1 9 | kind: Secret 10 | metadata: 11 | name: spinnaker-config 12 | type: Opaque 13 | data: 14 | {{ (.Files.Glob "static/*").AsSecrets | indent 2 }} -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:7 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json gulpfile.js yarn.lock /usr/src/app/ 9 | 10 | RUN yarn --pure-lockfile 11 | 12 | # Bundle app source 13 | COPY . /usr/src/app 14 | 15 | EXPOSE 3002 16 | 17 | CMD [ "npm", "run", "start" ] -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["semistandard", "plugin:react/recommended"], 3 | "env": { 4 | "es6": true, 5 | "browser": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "plugins": ["react"], 10 | "rules": { 11 | "no-plusplus": [2, {"allowForLoopAfterthoughts": true }], 12 | "jsx-quotes": ["error", "prefer-double"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_image-column.scss: -------------------------------------------------------------------------------- 1 | // .image-column { 2 | // &.data-flow { 3 | // transform: translateX(40%); 4 | // } 5 | 6 | // div:first-child { 7 | // position: absolute; 8 | // top: 15%; 9 | // } 10 | 11 | // div:last-child { 12 | // position: absolute; 13 | // top: 45%; 14 | // } 15 | // } 16 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | import App from './components/App'; 4 | import HomePage from './components/home/HomePage'; 5 | 6 | /** 7 | * Defines project routes. 8 | */ 9 | export default ( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /applications/puzzle/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:boron 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json yarn.lock /usr/src/app/ 9 | RUN yarn --pure-lockfile 10 | 11 | # Bundle app source 12 | COPY . /usr/src/app 13 | 14 | COPY up.sh /up.sh 15 | COPY down.sh /down.sh 16 | 17 | EXPOSE 3000 18 | CMD [ "node", "." ] 19 | -------------------------------------------------------------------------------- /manifests/etcd-cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "etcd.database.coreos.com/v1beta2" 2 | kind: "EtcdCluster" 3 | metadata: 4 | name: "example-etcd-cluster" 5 | ## Adding this annotation make this cluster managed by clusterwide operators 6 | ## namespaced operators ignore it 7 | # annotations: 8 | # etcd.database.coreos.com/scope: clusterwide 9 | spec: 10 | size: 3 11 | version: "3.2.13" 12 | 13 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_slider-container.scss: -------------------------------------------------------------------------------- 1 | .slider-container { 2 | display: flex; 3 | align-items: center; 4 | 5 | .slider-title { 6 | width: 40%; 7 | } 8 | .slider-box { 9 | width: 100%; 10 | } 11 | } 12 | 13 | .request-slider { 14 | .slider-container { 15 | .slider-box { 16 | width: 90%; 17 | margin: auto; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /applications/hello-kenzan/index.html: -------------------------------------------------------------------------------- 1 |

Hello from Kenzan! You've successfully built and run the Hello-Kenzan app.

2 |

The Hello-Kenzan app is a modified version of the nginx web server image. If you open up the kubernetes-ci-cd/hello-kenzan/DockerFile, you will note several things:

3 | -------------------------------------------------------------------------------- /applications/monitor-scale/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:boron 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package.json yarn.lock /usr/src/app/ 9 | 10 | RUN yarn --pure-lockfile 11 | 12 | # Bundle app source 13 | COPY . /usr/src/app 14 | 15 | #Expose port 3001 so the monitor service is reachable 16 | EXPOSE 3001 17 | EXPOSE 8080 18 | 19 | CMD ["node", "index.js"] 20 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 2 | ======================= 3 | 4 | 1. Wait until all pod are up with 5 | kubectl get pods --namespace spinnaker 6 | 7 | 2. Forward deck to localhost:9000 with 8 | kubectl --namespace spinnaker port-forward (kubectl --namespace spinnaker get pods -l app=deck -o jsonpath='{.items[*].metadata.name}') 9000:9000 9 | 10 | 3. Open http://localhost:9000 to start using spinnaker 11 | 12 | ======================= -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { composeWithDevTools } from 'redux-devtools-extension'; 4 | import rootReducer from '../reducers/index'; 5 | 6 | export default function configureStore (initialState) { 7 | return createStore( 8 | rootReducer, 9 | initialState, 10 | composeWithDevTools( 11 | applyMiddleware(thunk), 12 | ), 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /applications/puzzle/server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "restApiRoot": "/puzzle/v1", 3 | "host": "0.0.0.0", 4 | "port": 3000, 5 | "remoting": { 6 | "context": false, 7 | "rest": { 8 | "handleErrors": false, 9 | "normalizeHttpPath": false, 10 | "xml": false 11 | }, 12 | "json": { 13 | "strict": false, 14 | "limit": "100kb" 15 | }, 16 | "urlencoded": { 17 | "extended": true, 18 | "limit": "100kb" 19 | }, 20 | "cors": false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/etcd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "initializing Helm" 4 | helm init --wait --debug 5 | 6 | echo "installing etcd operator (Helm Chart)" 7 | helm install stable/etcd-operator --version 0.8.0 --name etcd-operator --debug 8 | kubectl rollout status deploy/tiller-deploy -n kube-system 9 | 10 | kubectl create -f manifests/etcd-cluster.yaml 11 | 12 | echo "installing etcd cluster service" 13 | kubectl create -f manifests/etcd-service.yaml 14 | 15 | echo "waiting for etcd cluster to turnup" 16 | sleep 10 17 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/test/helpers/setup-browser-env.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: 0 */ 2 | 3 | import browserEnv from 'browser-env'; 4 | 5 | browserEnv(['document', 'window', 'navigator']); 6 | 7 | /* 8 | ** Fix required for testing bug with react-slick 9 | ** https://github.com/akiran/react-slick/issues/93 10 | */ 11 | window.matchMedia = window.matchMedia || function () { 12 | return { 13 | matches: false, 14 | addListener: () => {}, 15 | removeListener: () => {} 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/README.md: -------------------------------------------------------------------------------- 1 | ## Kr8ssword Puzzle Kubernetes CI / CD , Microservices Example 2 | 3 | # setup 4 | From the root directory of the repository, use 'npm run part1' to setup the minikube cluster. 5 | 6 | ### Building 7 | Use or review /scripts/kr8sswordz-pages.sh for build/deployment steps 8 | 9 | ## Load application 10 | Run 'minikube service kr8sswordz' 11 | 12 | or 13 | 14 | Change MINIKUBEIP to your minikube ip and load the application below 15 | 16 | http://kr8sswordz.MINIKUBEIP.xip.io/ 17 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_data-flow-arrow.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | .data-flow > svg { 4 | transform: translateX(40%); 5 | height: 5rem; 6 | width: 5rem; 7 | opacity: 0; 8 | fill: $kubernetes-blue; 9 | position: absolute; 10 | left: -110px; 11 | transform: translate(10%, -50%); 12 | 13 | &.active { 14 | animation: fade 500ms ease-in-out; 15 | animation-direction: alternate; 16 | animation-duration: 1s; 17 | } 18 | } 19 | 20 | @keyframes fade { 21 | 100% {opacity: 1;} 22 | } 23 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/shared/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {IndexLink, Link} from 'react-router'; 3 | 4 | const Header = () => { 5 | return ( 6 |
7 | 8 |

Kr8sswordz

9 |
10 | ); 11 | }; 12 | 13 | export default Header; 14 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/utils/_variables.scss: -------------------------------------------------------------------------------- 1 | // Shades 2 | $true-white: #fff; 3 | $off-white: #eee; 4 | $kubernetes-blue: #158d8b; 5 | $transparent-black: rgba(0,0,0,0.2); 6 | $base-font-size: 1vw; 7 | $brand-blue: #303e48; 8 | $brand-gray: #dee0e2; 9 | $brand-dark-gray: #989fa4; 10 | $primary-border-color: #333; 11 | 12 | $highlight-green: #9ece35; 13 | $cell-size: 2rem; 14 | $nbr-of-horizontal-cells: 12; 15 | $page-indent: 3rem; 16 | $onesspace:2px; 17 | 18 | $container-padding: 1vw; 19 | 20 | // Font 21 | $open-sans: 'Roboto', sans-serif; 22 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Kubernetes CICD Kr8ssword Puzzle Demo Site 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/values.yaml: -------------------------------------------------------------------------------- 1 | front50: 2 | image: quay.io/spinnaker/front50 3 | tag: v1.69.0 4 | 5 | clouddriver: 6 | image: quay.io/spinnaker/clouddriver 7 | tag: v1.490.0 8 | 9 | orca: 10 | image: quay.io/spinnaker/orca 11 | tag: v1.304.0 12 | 13 | echo: 14 | image: quay.io/spinnaker/echo 15 | tag: v1.132.0 16 | 17 | gate: 18 | image: quay.io/spinnaker/gate 19 | tag: v3.1.0 20 | 21 | igor: 22 | image: quay.io/spinnaker/igor 23 | tag: v1.64.0 24 | 25 | deck: 26 | image: quay.io/spinnaker/deck 27 | tag: v2.1028.0 -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/atoms/_buttons.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | button, .button { 4 | cursor: pointer; 5 | border: none; 6 | border-radius: 2px; 7 | font-weight: 200; 8 | color: $true-white; 9 | padding: 0.5rem 1rem; 10 | font-size: 0.9rem; 11 | 12 | &.primary { 13 | background-color: $brand-blue; 14 | } 15 | &.secondary { 16 | background-color: $brand-dark-gray; 17 | } 18 | 19 | &.compact { 20 | font-size: 0.8rem; 21 | padding: 0.5rem; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /applications/monitor-scale/README.md: -------------------------------------------------------------------------------- 1 | === monitor-scale server === 2 | This appication provides an API to track when servers go up and down, caching that data and sending the list of crossword servers to the frontend using websockets. Additionally, this API provides an endpoint to scale the crossword deployment. 3 | 4 | ## Kr8sswordz Monitor-Scale Kubernetes CI / CD , Microservices Example 5 | 6 | # setup 7 | From the root directory of the repository, use 'npm run part1' to setup the minikube cluster. 8 | 9 | ### Building 10 | Use or review /scripts/monitor-scale.sh for build/deployment steps -------------------------------------------------------------------------------- /applications/puzzle/README.md: -------------------------------------------------------------------------------- 1 | === puzzle server === 2 | 3 | ## Kr8sswordz Puzzle Kubernetes CI / CD , Microservices Example 4 | 5 | # setup 6 | From the root directory of the repository, use 'npm run part1' to setup the minikube cluster. 7 | 8 | ### Building (Note: Lifecycle hooks in the k8s/deployment.yaml require the monitor-scale service to be up) 9 | Use or review /scripts/puzzle.sh for build/deployment steps 10 | 11 | ## Load application 12 | Change MINIKUBEIP to your minikube ip and use the URL below to review the puzzle API 13 | 14 | http://puzzle.MINIKUBEIP.xip.io/explorer 15 | 16 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/atoms/_slider.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | .slider { 4 | height: 2px; 5 | background-color: $brand-dark-gray; 6 | } 7 | 8 | .handle { 9 | height: 12px; 10 | width: 12px; 11 | background-color: $true-white; 12 | border: 3px solid $brand-dark-gray; 13 | border-radius: 100%; 14 | top: -7px; 15 | } 16 | 17 | .slider-count { 18 | position: absolute; 19 | top: 12px; 20 | left: -9px; 21 | font-size: 0.9rem; 22 | width: 30px; 23 | height: 30px; 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | } 28 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/organisms/instance-grid.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | .instance-grid { 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: space-between; 7 | align-items: flex-end; 8 | align-content: flex-end; 9 | height: 28rem; 10 | background-color: $true-white; 11 | border: 1px solid $brand-dark-gray; 12 | box-sizing: border-box; 13 | } 14 | 15 | 16 | .instance-buttons { 17 | .slider-container { 18 | width: 75%; 19 | 20 | .slider { 21 | width: 90%; 22 | } 23 | } 24 | 25 | button { 26 | width: 25%; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import { Provider } from 'react-redux'; 5 | import { Router, browserHistory } from 'react-router'; 6 | import routes from './routes'; 7 | import configureStore from './store/configureStore'; 8 | 9 | const store = configureStore({}); 10 | /** 11 | * Maps the projects files to the index.html app container. 12 | */ 13 | render( 14 | 15 | 16 | , 17 | document.getElementById('app') 18 | ); 19 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/organisms/_header.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | .primary-header { 4 | width: 100%; 5 | height: 6rem; 6 | display: block; 7 | background-color: $true-white; 8 | border-bottom: 1px solid $brand-dark-gray; 9 | 10 | 11 | .k8color { 12 | color: $kubernetes-blue; 13 | } 14 | 15 | .title { 16 | text-align: center; 17 | margin: 0; 18 | font-size: 3vw; 19 | padding-top: 1rem; 20 | } 21 | 22 | a { 23 | position: absolute; 24 | .logo { 25 | margin: 1rem 0 1rem $page-indent; 26 | width: 10rem; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_cell.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | .cell { 4 | position: relative; 5 | width: $cell-size; 6 | height: $cell-size; 7 | background-color: $true-white; 8 | border: 1px solid $brand-blue; 9 | text-align: center; 10 | 11 | input { 12 | width: 100%; 13 | height: 100%; 14 | text-align: center; 15 | border:0; 16 | outline: 0; 17 | } 18 | 19 | &.isEmpty { 20 | background-color: $brand-blue; 21 | 22 | input { 23 | background-color: $brand-blue; 24 | } 25 | } 26 | 27 | input.incorrect { 28 | color: red; 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/home/Database.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import DataFlowArrow from './DataFlowArrow'; 3 | 4 | function Database (props) { 5 | return ( 6 |
7 | 8 | 9 | 10 |
11 | ); 12 | } 13 | 14 | Database.propTypes = { 15 | databaseName: PropTypes.string.isRequired, 16 | active: PropTypes.bool.isRequired 17 | }; 18 | 19 | export default Database; 20 | 21 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_puzzle.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | .puzzle { 4 | box-sizing: border-box; 5 | overflow: hidden; 6 | display: flex; 7 | flex-flow: row wrap; 8 | width: 24rem; 9 | } 10 | 11 | .puzzle-hints { 12 | width: 100%; //fix for flexbox text not wrapping in IE 13 | 14 | h6 { 15 | margin: 0; 16 | border-bottom: 1px solid $brand-gray 17 | } 18 | 19 | .hint-container { 20 | width: 100%; 21 | .hint-category { 22 | padding: 0.5rem 0; 23 | } 24 | 25 | ul > li { 26 | list-style-type: none; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_instance-box.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | .instance { 4 | display: flex; 5 | box-sizing: border-box; 6 | background-color: $kubernetes-blue; 7 | width: 100%; 8 | margin-top: 0.25rem; 9 | color: $true-white; 10 | align-items: center; 11 | justify-content: center; 12 | font-size: 1rem; 13 | height: 1.5rem; 14 | 15 | &.active { 16 | background-color: $kubernetes-blue; 17 | animation: fadein; 18 | animation-duration: 1s; 19 | } 20 | } 21 | 22 | @keyframes fadein { 23 | from { 24 | background: $highlight-green; 25 | } 26 | to { 27 | background: $kubernetes-blue; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /applications/hello-kenzan/Jenkinsfile: -------------------------------------------------------------------------------- 1 | node { 2 | 3 | checkout scm 4 | 5 | env.DOCKER_API_VERSION="1.23" 6 | 7 | sh "git rev-parse --short HEAD > commit-id" 8 | 9 | tag = readFile('commit-id').replace("\n", "").replace("\r", "") 10 | appName = "hello-kenzan" 11 | registryHost = "127.0.0.1:30912/" 12 | imageName = "${registryHost}${appName}:${tag}" 13 | env.BUILDIMG=imageName 14 | 15 | stage "Build" 16 | 17 | sh "docker build -t ${imageName} part1/hello-kenzan" 18 | 19 | stage "Push" 20 | 21 | sh "docker push ${imageName}" 22 | 23 | stage "Deploy" 24 | 25 | sh "sed 's#__IMAGE__#'$BUILDIMG'#' part1/hello-kenzan/k8s/deployment.yaml | kubectl apply -f -" 26 | } -------------------------------------------------------------------------------- /applications/puzzle/Jenkinsfile: -------------------------------------------------------------------------------- 1 | node { 2 | 3 | checkout scm 4 | 5 | env.DOCKER_API_VERSION="1.23" 6 | 7 | sh "git rev-parse --short HEAD > commit-id" 8 | 9 | tag = readFile('commit-id').replace("\n", "").replace("\r", "") 10 | appName = "puzzle" 11 | registryHost = "127.0.0.1:30400/" 12 | imageName = "${registryHost}${appName}:${tag}" 13 | env.BUILDIMG=imageName 14 | env.BUILD_TAG=tag 15 | 16 | stage "Build" 17 | 18 | sh "docker build -t ${imageName} applications/puzzle" 19 | 20 | stage "Push" 21 | 22 | sh "docker push ${imageName}" 23 | 24 | stage "Deploy" 25 | 26 | kubernetesDeploy configs: "applications/${appName}/k8s/*.yaml", kubeconfigId: 'kenzan_kubeconfig' 27 | } 28 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/templates/_home-page.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | .home-page { 4 | padding: 1.5rem $page-indent; 5 | padding-bottom: 0; 6 | position: relative; 7 | display: flex; 8 | justify-content: space-between; 9 | 10 | //Data Flow Arrows 11 | .data-flow { 12 | width: 7%; 13 | } 14 | 15 | //Puzzle Box 16 | .crossword-container { 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | width: 25%; 21 | } 22 | 23 | //Instances 24 | .instances { 25 | width: 35%; 26 | } 27 | 28 | //Databases 29 | .dbs { 30 | width: 25%; 31 | .db-image:first-child { 32 | border-bottom: 1px solid $brand-dark-gray; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /applications/puzzle/server/boot/create-sample-models.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(app) { 4 | var words = require('./puzzle.json'); 5 | 6 | app.dataSources.db.automigrate('Crossword', function(err) { 7 | if (err) throw err; 8 | app.models.Crossword.find(function(err, crosswords) { 9 | if (err) throw err; 10 | console.log("Found " + crosswords.length + " existing crosswords"); 11 | if(!crosswords.length) { 12 | app.models.Crossword.create({ 13 | fromCache: false, 14 | words: words, 15 | }, function (err, crossword) { 16 | if (err) throw err; 17 | console.log('Model created: \n', crossword); 18 | }); 19 | } 20 | }); 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | node { 2 | 3 | checkout scm 4 | 5 | env.DOCKER_API_VERSION="1.23" 6 | 7 | sh "git rev-parse --short HEAD > commit-id" 8 | 9 | tag = readFile('commit-id').replace("\n", "").replace("\r", "") 10 | appName = "hello-kenzan" 11 | registryHost = "127.0.0.1:30400/" 12 | imageName = "${registryHost}${appName}:${tag}" 13 | env.BUILDIMG=imageName 14 | 15 | stage "Build" 16 | 17 | sh "docker build -t ${imageName} -f applications/hello-kenzan/Dockerfile applications/hello-kenzan" 18 | 19 | stage "Push" 20 | 21 | sh "docker push ${imageName}" 22 | 23 | stage "Deploy" 24 | 25 | kubernetesDeploy configs: "applications/${appName}/k8s/*.yaml", kubeconfigId: 'kenzan_kubeconfig' 26 | 27 | } -------------------------------------------------------------------------------- /applications/monitor-scale/Jenkinsfile: -------------------------------------------------------------------------------- 1 | node { 2 | 3 | checkout scm 4 | 5 | env.DOCKER_API_VERSION="1.23" 6 | 7 | sh "git rev-parse --short HEAD > commit-id" 8 | 9 | tag = readFile('commit-id').replace("\n", "").replace("\r", "") 10 | appName = "monitor-scale" 11 | registryHost = "127.0.0.1:30400/" 12 | imageName = "${registryHost}${appName}:${tag}" 13 | env.BUILDIMG=imageName 14 | env.BUILD_TAG=tag 15 | 16 | stage "Build" 17 | 18 | sh "docker build -t ${imageName} applications/monitor-scale" 19 | 20 | stage "Push" 21 | 22 | sh "docker push ${imageName}" 23 | 24 | stage "Deploy" 25 | 26 | kubernetesDeploy configs: "applications/${appName}/k8s/*.yaml", kubeconfigId: 'kenzan_kubeconfig' 27 | } -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/Jenkinsfile: -------------------------------------------------------------------------------- 1 | node { 2 | 3 | checkout scm 4 | 5 | env.DOCKER_API_VERSION="1.23" 6 | 7 | sh "git rev-parse --short HEAD > commit-id" 8 | 9 | tag = readFile('commit-id').replace("\n", "").replace("\r", "") 10 | appName = "kr8sswordz" 11 | registryHost = "127.0.0.1:30400/" 12 | imageName = "${registryHost}${appName}:${tag}" 13 | env.BUILDIMG=imageName 14 | env.BUILD_TAG=tag 15 | 16 | stage "Build" 17 | 18 | sh "docker build -t ${imageName} applications/kr8sswordz-pages" 19 | 20 | stage "Push" 21 | 22 | sh "docker push ${imageName}" 23 | 24 | stage "Deploy" 25 | 26 | kubernetesDeploy configs: "applications/kr8sswordz-pages/k8s/*.yaml", kubeconfigId: 'kenzan_kubeconfig' 27 | } -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/redis.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | # namespace: spinnaker 5 | name: redis 6 | labels: 7 | app: redis 8 | spec: 9 | ports: 10 | - port: 6379 11 | selector: 12 | app: redis 13 | tier: redis 14 | type: NodePort 15 | 16 | --- 17 | apiVersion: extensions/v1beta1 18 | kind: Deployment 19 | metadata: 20 | # namespace: spinnaker 21 | name: redis 22 | labels: 23 | app: redis 24 | spec: 25 | strategy: 26 | type: Recreate 27 | template: 28 | metadata: 29 | labels: 30 | app: redis 31 | tier: redis 32 | spec: 33 | containers: 34 | - image: redis:latest 35 | name: redis 36 | ports: 37 | - containerPort: 6379 38 | name: redis -------------------------------------------------------------------------------- /applications/hello-kenzan/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: hello-kenzan 5 | labels: 6 | app: hello-kenzan 7 | spec: 8 | ports: 9 | - port: 80 10 | targetPort: 80 11 | selector: 12 | app: hello-kenzan 13 | tier: hello-kenzan 14 | type: NodePort 15 | 16 | --- 17 | apiVersion: extensions/v1beta1 18 | kind: Deployment 19 | metadata: 20 | name: hello-kenzan 21 | labels: 22 | app: hello-kenzan 23 | spec: 24 | strategy: 25 | type: Recreate 26 | template: 27 | metadata: 28 | labels: 29 | app: hello-kenzan 30 | tier: hello-kenzan 31 | spec: 32 | containers: 33 | - image: 127.0.0.1:30400/hello-kenzan:$BUILD_TAG 34 | name: hello-kenzan 35 | ports: 36 | - containerPort: 80 37 | name: hello-kenzan -------------------------------------------------------------------------------- /applications/hello-kenzan/k8s/manual-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: hello-kenzan 5 | labels: 6 | app: hello-kenzan 7 | spec: 8 | ports: 9 | - port: 80 10 | targetPort: 80 11 | selector: 12 | app: hello-kenzan 13 | tier: hello-kenzan 14 | type: NodePort 15 | 16 | --- 17 | apiVersion: extensions/v1beta1 18 | kind: Deployment 19 | metadata: 20 | name: hello-kenzan 21 | labels: 22 | app: hello-kenzan 23 | spec: 24 | strategy: 25 | type: Recreate 26 | template: 27 | metadata: 28 | labels: 29 | app: hello-kenzan 30 | tier: hello-kenzan 31 | spec: 32 | containers: 33 | - image: 127.0.0.1:30400/hello-kenzan:latest 34 | name: hello-kenzan 35 | ports: 36 | - containerPort: 80 37 | name: hello-kenzan -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/actions/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const puzzle = { 2 | PUZZLE_LOADING: 'PUZZLE_LOADING', 3 | CLEAR_PUZZLE_DATA: 'CLEAR_PUZZLE_DATA', 4 | GET_PUZZLE_DATA_SUCCESS: 'GET_PUZZLE_DATA_SUCCESS', 5 | GET_PUZZLE_DATA_FAILURE: 'GET_PUZZLE_DATA_FAILURE', 6 | SUBMIT_PUZZLE_DATA_SUCCESS: 'SUBMIT_PUZZLE_DATA_SUCCESS', 7 | SUBMIT_PUZZLE_DATA_FAILURE: 'SUBMIT_PUZZLE_DATA_FAILURE', 8 | SENDING_DATA: 'SENDING_DATA', 9 | FROM_CACHE: 'FROM_CACHE', 10 | FROM_MONGO: 'FROM_MONGO' 11 | }; 12 | 13 | export const websocket = { 14 | CONNECTION_LOADING: 'CONNECTION_LOADING', 15 | CONNECT_TO_SOCKET: 'CONNECT_TO_SOCKET', 16 | DISCONNECT_FROM_SOCKET: 'DISCONNECT_FROM_SOCKET', 17 | GET_PODS: 'GET_PODS', 18 | POD_UP: 'POD_UP', 19 | POD_DOWN: 'POD_DOWN', 20 | ACTIVE_INSTANCE: 'ACTIVE_INSTANCE', 21 | SCALE: 'SCALE' 22 | }; 23 | -------------------------------------------------------------------------------- /manifests/all-services.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: monitor-scale 5 | labels: 6 | app: monitor-scale 7 | spec: 8 | ports: 9 | - port: 3001 10 | targetPort: 3001 11 | selector: 12 | app: monitor-scale 13 | tier: monitor-scale 14 | type: NodePort 15 | --- 16 | apiVersion: v1 17 | kind: Service 18 | metadata: 19 | name: puzzle 20 | labels: 21 | app: puzzle 22 | spec: 23 | ports: 24 | - port: 3000 25 | targetPort: 3000 26 | selector: 27 | app: puzzle 28 | tier: puzzle 29 | type: NodePort 30 | --- 31 | apiVersion: v1 32 | kind: Service 33 | metadata: 34 | name: kr8sswordz 35 | labels: 36 | app: kr8sswordz 37 | spec: 38 | ports: 39 | - port: 80 40 | targetPort: 3002 41 | selector: 42 | app: kr8sswordz 43 | tier: kr8sswordz 44 | type: NodePort -------------------------------------------------------------------------------- /manifests/monitor-scale-serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: monitor-scale 6 | namespace: default 7 | 8 | --- 9 | 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: Role 12 | metadata: 13 | namespace: default 14 | name: puzzle-scaler 15 | rules: 16 | - apiGroups: 17 | - "extensions" 18 | resources: 19 | - deployments 20 | - deployments/scale 21 | resourceNames: 22 | - "puzzle" 23 | verbs: 24 | - update 25 | - get 26 | 27 | --- 28 | 29 | apiVersion: rbac.authorization.k8s.io/v1 30 | kind: RoleBinding 31 | metadata: 32 | name: monitor-scale-puzzle-scaler 33 | namespace: default 34 | roleRef: 35 | kind: Role 36 | name: puzzle-scaler 37 | apiGroup: rbac.authorization.k8s.io 38 | subjects: 39 | - kind: ServiceAccount 40 | name: monitor-scale 41 | namespace: default 42 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/front50.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | address: 0.0.0.0 4 | redis: 5 | enabled: true 6 | connection: redis://redis:6379 7 | host: redis 8 | port: 6379 9 | scheduler: default 10 | parallelism: -1 11 | secure: false 12 | 13 | 14 | cassandra: 15 | enabled: false 16 | 17 | spinnaker: 18 | 19 | redis: 20 | enabled: true 21 | secure: false 22 | connection: redis://redis:6379 23 | host: redis 24 | port: 6379 25 | scheduler: default 26 | parallelism: -1 27 | 28 | cassandra: 29 | enabled: false 30 | 31 | 32 | swagger: 33 | enabled: true 34 | title: Spinnaker Front50 API 35 | description: 36 | contact: 37 | patterns: 38 | - /default/.* 39 | - /credentials.* 40 | - /global/.* 41 | - /notifications.* 42 | - /pipelines.* 43 | - /strategies.* 44 | - /v2/.* -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/home/DataFlowArrow.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | function DataFlowArrow (props) { 5 | const activeClass = classNames({ 6 | bottom: true, 7 | active: props.active 8 | }); 9 | 10 | return ( 11 |
12 | 13 |
14 | ); 15 | } 16 | 17 | DataFlowArrow.propTypes = { 18 | className: PropTypes.string.isRequired, 19 | active: PropTypes.bool 20 | }; 21 | 22 | export default DataFlowArrow; 23 | -------------------------------------------------------------------------------- /applications/monitor-scale/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monitor-scale", 3 | "version": "1.0.0", 4 | "description": "A service for updating the cache and emitting websocket events back to the frontend", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node ." 8 | }, 9 | "repository": "git+https://github.com/kenzanlabs/kubernetes-ci-cd.git", 10 | "keywords": [ 11 | "monitor", 12 | "cache", 13 | "blog" 14 | ], 15 | "author": "Evan Yeager", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/kenzanlabs/kubernetes-ci-cd/issues" 19 | }, 20 | "homepage": "https://github.com/kenzanlabs/kubernetes-ci-cd#readme", 21 | "dependencies": { 22 | "async": "2.2.0", 23 | "body-parser": "1.17.1", 24 | "cors": "2.8.3", 25 | "express": "4.15.2", 26 | "node-etcd": "5.0.3", 27 | "request": "2.81.0", 28 | "socket.io": "1.7.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /scripts/puzzle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Retrieve the latest git commit hash 4 | BUILD_TAG=`git rev-parse --short HEAD` 5 | 6 | #Build the docker image 7 | docker build -t 127.0.0.1:30400/puzzle:$BUILD_TAG -f applications/puzzle/Dockerfile applications/puzzle 8 | 9 | #Setup the proxy for the registry 10 | docker stop socat-registry; docker rm socat-registry; docker run -d -e "REG_IP=`minikube ip`" -e "REG_PORT=30400" --name socat-registry -p 30400:5000 socat-registry 11 | 12 | echo "5 second sleep to make sure the registry is ready" 13 | sleep 5; 14 | 15 | #Push the images 16 | docker push 127.0.0.1:30400/puzzle:$BUILD_TAG 17 | 18 | #Stop the registry proxy 19 | docker stop socat-registry 20 | 21 | # Create the deployment and service for the puzzle server aka puzzle 22 | sed 's#127.0.0.1:30400/puzzle:$BUILD_TAG#127.0.0.1:30400/puzzle:'$BUILD_TAG'#' applications/puzzle/k8s/deployment.yaml | kubectl apply -f - 23 | 24 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | cache: true, 5 | devtool: 'source-map', 6 | entry: { 7 | react: './src/index' 8 | }, 9 | resolve: { 10 | extensions: ['', '.js', '.json'] 11 | }, 12 | output: { 13 | path: path.join(__dirname, 'dist'), 14 | publicPath: 'dist/', 15 | filename: 'bundle.js', 16 | sourceMapFilename: '[file].map' 17 | }, 18 | module: { 19 | loaders: [ 20 | // required to write 'require('./style.css')' 21 | { test: /\.css$/, loader: 'style-loader!css-loader' }, 22 | 23 | // required for react jsx 24 | { test: /\.js$/, include: path.join(__dirname, 'src'), loaders: ['babel'] }, 25 | 26 | // "file" loader for svg 27 | { test: /\.svg$/, loader: 'file-loader' }, 28 | 29 | // json loader 30 | { test: /\.json$/, loader: 'json' } 31 | ] 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /applications/puzzle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puzzle", 3 | "version": "1.0.0", 4 | "main": "server/server.js", 5 | "scripts": { 6 | "lint": "eslint .", 7 | "start": "node .", 8 | "posttest": "npm run lint && nsp check" 9 | }, 10 | "dependencies": { 11 | "compression": "^1.0.3", 12 | "cors": "^2.5.2", 13 | "helmet": "^1.3.0", 14 | "loopback": "^3.0.0", 15 | "loopback-boot": "^2.6.5", 16 | "loopback-component-explorer": "^4.0.0", 17 | "loopback-connector-mongodb": "^3.0.1", 18 | "node-etcd": "^5.0.3", 19 | "serve-favicon": "^2.0.1", 20 | "strong-error-handler": "^1.0.1", 21 | "toastr": "^2.1.2" 22 | }, 23 | "devDependencies": { 24 | "eslint": "^2.13.1", 25 | "eslint-config-loopback": "^4.0.0", 26 | "nsp": "^2.1.0" 27 | }, 28 | "repository": { 29 | "type": "", 30 | "url": "" 31 | }, 32 | "license": "UNLICENSED", 33 | "description": "puzzle" 34 | } 35 | -------------------------------------------------------------------------------- /scripts/kr8sswordz-pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Retrieve the latest git commit hash 4 | BUILD_TAG=`git rev-parse --short HEAD` 5 | 6 | #Build the docker image 7 | docker build -t 127.0.0.1:30400/kr8sswordz:$BUILD_TAG -f applications/kr8sswordz-pages/Dockerfile applications/kr8sswordz-pages 8 | 9 | #Setup the proxy for the registry 10 | docker stop socat-registry; docker rm socat-registry; docker run -d -e "REG_IP=`minikube ip`" -e "REG_PORT=30400" --name socat-registry -p 30400:5000 socat-registry 11 | 12 | echo "5 second sleep to make sure the registry is ready" 13 | sleep 5; 14 | 15 | #Push the images 16 | docker push 127.0.0.1:30400/kr8sswordz:$BUILD_TAG 17 | 18 | #Stop the registry proxy 19 | docker stop socat-registry 20 | 21 | # Create the deployment and service for the front end aka kr8sswordz 22 | sed 's#127.0.0.1:30400/kr8sswordz:$BUILD_TAG#127.0.0.1:30400/kr8sswordz:'$BUILD_TAG'#' applications/kr8sswordz-pages/k8s/deployment.yaml | kubectl apply -f - 23 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/molecules/_loader.scss: -------------------------------------------------------------------------------- 1 | @import '../utils/variables'; 2 | 3 | .loader { 4 | border: 16px solid $brand-gray; 5 | border-top: 16px solid $kubernetes-blue; 6 | border-radius: 50%; 7 | width: 120px; 8 | height: 120px; 9 | animation: spin 2s linear infinite; 10 | } 11 | 12 | @keyframes spin { 13 | 100% { transform: rotate(360deg); } 14 | } 15 | 16 | .item-center { 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | } 21 | 22 | @mixin fadein-opacity($fade-time: 1s) { 23 | animation: fadein $fade-time ease-in-out forwards; 24 | } 25 | 26 | .background-fade { 27 | @include fadein-opacity(250ms); 28 | position: fixed; 29 | top: 0; 30 | left: 0; 31 | min-height: 100%; 32 | width: 100%; 33 | background-color: $transparent-black; 34 | z-index: 11; 35 | } 36 | 37 | 38 | @keyframes fadein { 39 | from { opacity: 0; } 40 | to { opacity: 1; } 41 | } 42 | -------------------------------------------------------------------------------- /applications/puzzle/server/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var loopback = require('loopback'); 4 | var boot = require('loopback-boot'); 5 | 6 | var app = module.exports = loopback(); 7 | 8 | app.start = function() { 9 | // start the web server 10 | return app.listen(function() { 11 | app.emit('started'); 12 | var baseUrl = app.get('url').replace(/\/$/, ''); 13 | console.log('Web server listening at: %s', baseUrl); 14 | if (app.get('loopback-component-explorer')) { 15 | var explorerPath = app.get('loopback-component-explorer').mountPath; 16 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 17 | } 18 | }); 19 | }; 20 | 21 | // Bootstrap the application, configure models, datasources and middleware. 22 | // Sub-apps like REST API are mounted via boot scripts. 23 | boot(app, __dirname, function(err) { 24 | if (err) throw err; 25 | 26 | // start the server if `$ node server.js` 27 | if (require.main === module) 28 | app.start(); 29 | }); 30 | -------------------------------------------------------------------------------- /scripts/etcd-crd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "installing etcd operator" 4 | kubectl create -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/deployment.yaml 5 | kubectl rollout status -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/deployment.yaml 6 | 7 | until kubectl get thirdpartyresource cluster.etcd.coreos.com 8 | do 9 | echo "waiting for operator" 10 | sleep 2 11 | done 12 | 13 | echo "pausing for 10 seconds for operator to settle" 14 | sleep 10 15 | 16 | kubectl create -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/example-etcd-cluster.yaml 17 | 18 | echo "installing etcd cluster service" 19 | kubectl create -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/example-etcd-cluster-nodeport-service.json 20 | 21 | echo "waiting for etcd cluster to turnup" 22 | 23 | until kubectl get pod example-etcd-cluster-0002 24 | do 25 | echo "waiting for etcd cluster to turnup" 26 | sleep 2 27 | done 28 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/echo.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | # namespace: spinnaker 5 | name: echo 6 | labels: 7 | app: echo 8 | spec: 9 | ports: 10 | - port: 8089 11 | targetPort: 8089 12 | selector: 13 | app: echo 14 | tier: echo 15 | type: NodePort 16 | 17 | --- 18 | apiVersion: extensions/v1beta1 19 | kind: Deployment 20 | metadata: 21 | # namespace: spinnaker 22 | name: echo 23 | labels: 24 | app: echo 25 | spec: 26 | strategy: 27 | type: Recreate 28 | template: 29 | metadata: 30 | labels: 31 | app: echo 32 | tier: echo 33 | spec: 34 | containers: 35 | - image: {{.Values.echo.image}}:{{.Values.echo.tag}} 36 | name: echo 37 | ports: 38 | - containerPort: 8089 39 | name: echo 40 | volumeMounts: 41 | - name: spinnaker-config 42 | mountPath: /opt/echo/config 43 | volumes: 44 | - name: spinnaker-config 45 | configMap: 46 | name: spinnaker-config -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/gate.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | # namespace: spinnaker 5 | name: gate 6 | labels: 7 | app: gate 8 | spec: 9 | ports: 10 | - port: 8084 11 | targetPort: 8084 12 | selector: 13 | app: gate 14 | tier: gate 15 | type: NodePort 16 | 17 | --- 18 | apiVersion: extensions/v1beta1 19 | kind: Deployment 20 | metadata: 21 | # namespace: spinnaker 22 | name: gate 23 | labels: 24 | app: gate 25 | spec: 26 | strategy: 27 | type: Recreate 28 | template: 29 | metadata: 30 | labels: 31 | app: gate 32 | tier: gate 33 | spec: 34 | containers: 35 | - image: {{.Values.gate.image}}:{{.Values.gate.tag}} 36 | name: gate 37 | ports: 38 | - containerPort: 8084 39 | name: gate 40 | volumeMounts: 41 | - name: spinnaker-config 42 | mountPath: /opt/gate/config 43 | volumes: 44 | - name: spinnaker-config 45 | configMap: 46 | name: spinnaker-config -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/igor.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | # namespace: spinnaker 5 | name: igor 6 | labels: 7 | app: igor 8 | spec: 9 | ports: 10 | - port: 8088 11 | targetPort: 8088 12 | selector: 13 | app: igor 14 | tier: igor 15 | type: NodePort 16 | 17 | --- 18 | apiVersion: extensions/v1beta1 19 | kind: Deployment 20 | metadata: 21 | # namespace: spinnaker 22 | name: igor 23 | labels: 24 | app: igor 25 | spec: 26 | strategy: 27 | type: Recreate 28 | template: 29 | metadata: 30 | labels: 31 | app: igor 32 | tier: igor 33 | spec: 34 | containers: 35 | - image: {{.Values.igor.image}}:{{.Values.igor.tag}} 36 | name: igor 37 | ports: 38 | - containerPort: 8084 39 | name: igor 40 | volumeMounts: 41 | - name: spinnaker-config 42 | mountPath: /opt/igor/config 43 | volumes: 44 | - name: spinnaker-config 45 | configMap: 46 | name: spinnaker-config -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/orca.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | # namespace: spinnaker 5 | name: orca 6 | labels: 7 | app: orca 8 | spec: 9 | ports: 10 | - port: 8083 11 | targetPort: 8083 12 | selector: 13 | app: orca 14 | tier: orca 15 | type: NodePort 16 | 17 | --- 18 | apiVersion: extensions/v1beta1 19 | kind: Deployment 20 | metadata: 21 | # namespace: spinnaker 22 | name: orca 23 | labels: 24 | app: orca 25 | spec: 26 | strategy: 27 | type: Recreate 28 | template: 29 | metadata: 30 | labels: 31 | app: orca 32 | tier: orca 33 | spec: 34 | containers: 35 | - image: {{.Values.orca.image}}:{{.Values.orca.tag}} 36 | name: orca 37 | ports: 38 | - containerPort: 8083 39 | name: orca 40 | volumeMounts: 41 | - name: spinnaker-config 42 | mountPath: /opt/orca/config 43 | volumes: 44 | - name: spinnaker-config 45 | configMap: 46 | name: spinnaker-config -------------------------------------------------------------------------------- /scripts/monitor-scale.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Retrieve the latest git commit hash 4 | BUILD_TAG=`git rev-parse --short HEAD` 5 | 6 | #Build the docker image 7 | docker build -t 127.0.0.1:30400/monitor-scale:$BUILD_TAG -f applications/monitor-scale/Dockerfile applications/monitor-scale 8 | 9 | #Setup the proxy for the registry 10 | docker stop socat-registry; docker rm socat-registry; docker run -d -e "REG_IP=`minikube ip`" -e "REG_PORT=30400" --name socat-registry -p 30400:5000 socat-registry 11 | 12 | echo "5 second sleep to make sure the registry is ready" 13 | sleep 5; 14 | 15 | #Push the images 16 | docker push 127.0.0.1:30400/monitor-scale:$BUILD_TAG 17 | 18 | #Stop the registry proxy 19 | docker stop socat-registry 20 | 21 | # Setup RBAC auth for monitor-scale 22 | kubectl apply -f manifests/monitor-scale-serviceaccount.yaml 23 | 24 | # Create the deployment and service for the monitor-scale node server 25 | sed 's#127.0.0.1:30400/monitor-scale:$BUILD_TAG#127.0.0.1:30400/monitor-scale:'$BUILD_TAG'#' applications/monitor-scale/k8s/deployment.yaml | kubectl apply -f - 26 | -------------------------------------------------------------------------------- /applications/puzzle/server/middleware.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial:before": { 3 | "loopback#favicon": {} 4 | }, 5 | "initial": { 6 | "compression": {}, 7 | "cors": { 8 | "params": { 9 | "origin": true, 10 | "credentials": true, 11 | "maxAge": 86400 12 | } 13 | }, 14 | "helmet#xssFilter": {}, 15 | "helmet#frameguard": { 16 | "params": [ 17 | "deny" 18 | ] 19 | }, 20 | "helmet#hsts": { 21 | "params": { 22 | "maxAge": 0, 23 | "includeSubdomains": true 24 | } 25 | }, 26 | "helmet#hidePoweredBy": {}, 27 | "helmet#ieNoOpen": {}, 28 | "helmet#noSniff": {}, 29 | "helmet#noCache": { 30 | "enabled": false 31 | } 32 | }, 33 | "session": {}, 34 | "auth": {}, 35 | "parse": {}, 36 | "routes": { 37 | "loopback#rest": { 38 | "paths": [ 39 | "${restApiRoot}" 40 | ] 41 | } 42 | }, 43 | "files": {}, 44 | "final": { 45 | "loopback#urlNotFound": {} 46 | }, 47 | "final:after": { 48 | "strong-error-handler": {} 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /applications/jenkins/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins/jenkins 2 | USER root 3 | 4 | #Pre-Install Jenkins Plugins 5 | COPY plugins.txt /usr/share/jenkins/ref/plugins.txt 6 | RUN jenkins-plugin-cli --plugin-file /usr/share/jenkins/ref/plugins.txt 7 | 8 | #Installing Docker 9 | RUN apt-get update && apt-get install software-properties-common apt-transport-https ca-certificates -y; \ 10 | curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -;\ 11 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable";\ 12 | apt-get update && apt-get install docker-ce -y 13 | 14 | #Installing kubectl from Docker 15 | RUN curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -;\ 16 | touch /etc/apt/sources.list.d/kubernetes.list;\ 17 | echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list;\ 18 | apt-get update && apt-get install -y kubectl 19 | 20 | # Grant jenkins user group access to /var/run/docker.sock 21 | RUN addgroup --gid 1001 dsock 22 | RUN gpasswd -a jenkins dsock 23 | USER jenkins 24 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/front50.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | # namespace: spinnaker 5 | name: front50 6 | labels: 7 | app: front50 8 | spec: 9 | ports: 10 | - port: 8080 11 | targetPort: 8080 12 | selector: 13 | app: front50 14 | tier: front50 15 | type: NodePort 16 | 17 | --- 18 | apiVersion: extensions/v1beta1 19 | kind: Deployment 20 | metadata: 21 | # namespace: spinnaker 22 | name: front50 23 | labels: 24 | app: front50 25 | spec: 26 | strategy: 27 | type: Recreate 28 | template: 29 | metadata: 30 | labels: 31 | app: front50 32 | tier: front50 33 | spec: 34 | containers: 35 | - image: {{.Values.front50.image}}:{{.Values.front50.tag}} 36 | name: front50 37 | ports: 38 | - containerPort: 8080 39 | name: front50 40 | volumeMounts: 41 | - name: spinnaker-config 42 | mountPath: /opt/front50/config 43 | volumes: 44 | - name: spinnaker-config 45 | configMap: 46 | name: spinnaker-config -------------------------------------------------------------------------------- /applications/puzzle/server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "loopback/common/models", 5 | "loopback/server/models", 6 | "../common/models", 7 | "./models" 8 | ], 9 | "mixins": [ 10 | "loopback/common/mixins", 11 | "loopback/server/mixins", 12 | "../common/mixins", 13 | "./mixins" 14 | ] 15 | }, 16 | "User": { 17 | "dataSource": "db", 18 | "public": false 19 | }, 20 | "AccessToken": { 21 | "dataSource": "db", 22 | "public": false 23 | }, 24 | "ACL": { 25 | "dataSource": "db", 26 | "public": false 27 | }, 28 | "RoleMapping": { 29 | "dataSource": "db", 30 | "public": false, 31 | "options": { 32 | "strictObjectIDCoercion": true 33 | } 34 | }, 35 | "Role": { 36 | "dataSource": "db", 37 | "public": false 38 | }, 39 | "Crossword": { 40 | "dataSource": "db", 41 | "options": { 42 | "remoting": { 43 | "sharedMethods": { 44 | "*": false, 45 | "get": true, 46 | "put": true, 47 | "clear": true 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/styleguide-config.js: -------------------------------------------------------------------------------- 1 | const styleguide = require('sc5-styleguide/lib/modules/cli/styleguide-cli'); 2 | const async = require('async'); 3 | const {ncp} = require('ncp'); 4 | 5 | const config = { 6 | title: 'Styleguide', 7 | sideNav: true, 8 | overviewPath: 'src/styles/STYLEGUIDE.md', 9 | commonClass: 'sg-common', 10 | kssSource: 'src/**/*.scss', 11 | styleSource: 'dist/style.css', 12 | output: 'styleguide', 13 | watch: true, 14 | server: true, 15 | hideSubsectionsOnMainSection: true, 16 | disableEncapsulation: true 17 | }; 18 | 19 | async.series([ 20 | (done) => { 21 | styleguide({ 22 | overviewPath: 'src/styles/STYLEGUIDE.md', 23 | kssSource: 'src/**/*.scss', 24 | styleSource: 'dist/style.css', 25 | output: 'styleguide', 26 | watch: false, 27 | server: false 28 | }); 29 | done(); 30 | }, 31 | (done) => { 32 | ncp('src/assets', 'styleguide/assets', (err) => { 33 | if (err) { 34 | console.log(err); 35 | } 36 | console.log('Assets copied to styleguide'); 37 | done(); 38 | }); 39 | }, 40 | () => { 41 | styleguide(config); 42 | } 43 | ]); 44 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/README.md: -------------------------------------------------------------------------------- 1 | # Spinnaker Helm Chart 2 | 3 | ## Install Spinnaker on your kubernetes cluster with one command. 4 | 5 | Clouddriver auto-configured to deploy to the same cluster. 6 | 7 | Ensure `kubectl` is pointed at your cluster 8 | ``` 9 | kubectl cluster-info 10 | ``` 11 | 12 | Initialize `helm` on your cluster 13 | ``` 14 | helm init 15 | ``` 16 | 17 | Clone repo and install Spinnaker in it's own namespace 18 | ``` 19 | git clone https://github.com/moondev/spinnaker-helm.git; cd spinnaker-helm 20 | 21 | helm install --namespace spinnaker --name spinnaker ./spinnaker-chart 22 | ``` 23 | 24 | Wait until all pods are up 25 | ``` 26 | kubectl get pods --namespace spinnaker 27 | ``` 28 | 29 | Forward port 9000 to deck pod. 30 | ``` 31 | kubectl --namespace spinnaker port-forward (kubectl --namespace spinnaker get pods -l app=deck -o jsonpath='{.items[*].metadata.name}') 9000:9000 32 | ``` 33 | 34 | Spinnaker is now available for use at `http://localhost:9000` 35 | 36 | 37 | To delete the release and remove all Spinnaker components 38 | 39 | ``` 40 | helm delete spinnaker --purge 41 | ``` 42 | 43 | Versions of Spinnaker components can be edited in `spinnaker-chart/values.yaml` -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/assets/database.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/orca.yml: -------------------------------------------------------------------------------- 1 | bakery: 2 | allowMissingPackageInstallation: true 3 | baseUrl: http://rosco:8087 4 | extractBuildDetails: true 5 | roscoApisEnabled: true 6 | propagateCloudProviderType: true 7 | 8 | rosco: 9 | baseUrl: http://rosco:8007 10 | roscoApisEnabled: true 11 | enabled: false 12 | allowMissingPackageInstallation: true 13 | 14 | default: 15 | bake: 16 | account: docker 17 | 18 | securityGroups: null 19 | vpc: 20 | securityGroups: null 21 | echo: 22 | baseUrl: http://echo:8089 23 | enabled: false 24 | flex: 25 | baseUrl: http://not-a-host 26 | front50: 27 | baseUrl: http://front50:8080 28 | 29 | igor: 30 | enabled: true 31 | baseUrl: http://igor:8088 32 | kato: 33 | baseUrl: http://clouddriver:7002 34 | mort: 35 | baseUrl: http://clouddriver:7002 36 | oort: 37 | baseUrl: http://clouddriver:7002 38 | redis: 39 | enabled: true 40 | connection: redis://redis:6379 41 | scheduler: default 42 | parallelism: -1 43 | server: 44 | port: 8083 45 | address: 0.0.0.0 46 | services: 47 | orca: 48 | timezone: west 49 | tasks: 50 | executionWindow: 51 | timezone: west 52 | tide: 53 | baseUrl: http://not-a-host 54 | enabled: false -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kubernetes-ci-cd", 3 | "version": "1.0.0", 4 | "description": "## Get started Requirements * nodejs * kubectl * virtualbox * minikube * At least 4GB of available memory, 8GB for part 5", 5 | "main": "start.js", 6 | "dependencies": { 7 | "cmdify": "^0.0.4", 8 | "colors": "^1.1.2", 9 | "fetch-retry": "^1.1.0", 10 | "inquirer": "^3.0.6", 11 | "rx": "^4.1.0", 12 | "yamljs": "^0.2.8" 13 | }, 14 | "devDependencies": {}, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1", 17 | "start": "echo 'Begin the current interactive tutorial by executing: npm run where can be part1, part2, part3 or part4'", 18 | "part1": "node start.js part1.yml", 19 | "part2": "node start.js part2.yml", 20 | "part3": "node start.js part3.yml", 21 | "part4": "node start.js part4.yml" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/kenzanlabs/kubernetes-ci-cd.git" 26 | }, 27 | "author": "", 28 | "license": "ISC", 29 | "bugs": { 30 | "url": "https://github.com/kenzanlabs/kubernetes-ci-cd/issues" 31 | }, 32 | "homepage": "https://github.com/kenzanlabs/kubernetes-ci-cd#readme" 33 | } 34 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/style.scss: -------------------------------------------------------------------------------- 1 | // Library imports 2 | @import '//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css'; 3 | 4 | // Utilities 5 | @import 'styles/utils/utils'; 6 | 7 | // Atoms 8 | @import 'styles/atoms/atoms'; 9 | 10 | // Molecules 11 | @import 'styles/molecules/molecules'; 12 | 13 | // Organisms 14 | @import 'styles/organisms/organisms'; 15 | 16 | // Templates 17 | @import 'styles/templates/templates'; 18 | 19 | // Extend common html styles 20 | %html { 21 | box-sizing: content-box; 22 | font-size: $base-font-size; 23 | 24 | * { 25 | &, 26 | &:before, 27 | &:after { 28 | box-sizing: inherit; 29 | } 30 | } 31 | } 32 | 33 | // Extend common body styles 34 | %body { 35 | font-family: 'Roboto', sans-serif; 36 | margin: 0; 37 | color: $brand-blue; 38 | } 39 | 40 | // Styles for html element in theme 41 | html { 42 | @extend %html; 43 | } 44 | 45 | // Styles for body element in theme 46 | body { 47 | background: $off-white; 48 | @extend %body; 49 | } 50 | 51 | ul { 52 | margin: 0; 53 | padding: 0; 54 | } 55 | 56 | // Combine html styles and body styles for commonClass wrapper 57 | // .sg-common { 58 | // @extend %html; 59 | // @extend %body; 60 | // } 61 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: kr8sswordz 5 | labels: 6 | app: kr8sswordz 7 | spec: 8 | ports: 9 | - port: 80 10 | targetPort: 3002 11 | selector: 12 | app: kr8sswordz 13 | tier: kr8sswordz 14 | type: NodePort 15 | 16 | --- 17 | apiVersion: extensions/v1beta1 18 | kind: Deployment 19 | metadata: 20 | name: kr8sswordz 21 | labels: 22 | app: kr8sswordz 23 | spec: 24 | strategy: 25 | type: Recreate 26 | template: 27 | metadata: 28 | labels: 29 | app: kr8sswordz 30 | tier: kr8sswordz 31 | spec: 32 | containers: 33 | - image: 127.0.0.1:30400/kr8sswordz:$BUILD_TAG 34 | name: kr8sswordz 35 | imagePullPolicy: Always 36 | ports: 37 | - containerPort: 3002 38 | name: kr8sswordz 39 | --- 40 | apiVersion: extensions/v1beta1 41 | kind: Ingress 42 | metadata: 43 | name: kr8sswordz 44 | spec: 45 | backend: 46 | serviceName: kr8sswordz 47 | servicePort: 80 48 | rules: 49 | - host: kr8sswordz.192.168.56.100.nip.io 50 | http: 51 | paths: 52 | - path: / 53 | backend: 54 | serviceName: kr8sswordz 55 | servicePort: 80 56 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/shared/Cell.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import classNames from 'classnames'; 3 | 4 | function Cell (props) { 5 | const cellClass = classNames({ 6 | cell: true, 7 | isEmpty: props.isEmpty 8 | }); 9 | 10 | const currentVal = props.value === '*' ? '' : props.value; 11 | const inputClass = classNames({ 12 | incorrect: currentVal && currentVal.toLowerCase() !== props.letter.toLowerCase() 13 | }); 14 | 15 | return ( 16 |
17 |
{props.positionInWord === 0 ? props.wordNbr : ''}
18 | 28 |
29 | ); 30 | } 31 | 32 | Cell.propTypes = { 33 | id: PropTypes.string.isRequired, 34 | isEmpty: PropTypes.bool.isRequired, 35 | positionInWord: PropTypes.number, 36 | wordNbr: PropTypes.number, 37 | letter: PropTypes.string, 38 | value: PropTypes.string, 39 | onCellInput: PropTypes.func 40 | }; 41 | 42 | export default Cell; 43 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/deck.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | # namespace: spinnaker 5 | name: deck 6 | labels: 7 | app: deck 8 | spec: 9 | ports: 10 | - port: 80 11 | targetPort: 9000 12 | selector: 13 | app: deck 14 | tier: deck 15 | type: NodePort 16 | 17 | --- 18 | apiVersion: extensions/v1beta1 19 | kind: Deployment 20 | metadata: 21 | # namespace: spinnaker 22 | name: deck 23 | labels: 24 | app: deck 25 | spec: 26 | strategy: 27 | type: Recreate 28 | template: 29 | metadata: 30 | labels: 31 | app: deck 32 | tier: deck 33 | spec: 34 | containers: 35 | - image: {{.Values.deck.image}}:{{.Values.deck.tag}} 36 | name: deck 37 | ports: 38 | - containerPort: 9000 39 | name: deck 40 | volumeMounts: 41 | - name: spinnaker-config 42 | mountPath: /settings.js 43 | subPath: settings.js 44 | - name: spinnaker-config 45 | mountPath: /opt/deck/docker/run-apache2.sh 46 | subPath: run-apache2.sh 47 | volumes: 48 | - name: spinnaker-config 49 | configMap: 50 | name: spinnaker-config 51 | defaultMode: 0777 -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Header from './shared/Header'; 4 | import Loader from './shared/Loader'; 5 | 6 | /** 7 | * The App component for the project. 8 | */ 9 | class App extends React.Component { 10 | render () { 11 | let loading = false; 12 | if (this.props.webSocketLoading || this.props.puzzleLoading) { 13 | loading = true; 14 | } 15 | 16 | return ( 17 |
18 | {loading && 19 | 20 | } 21 |
22 | {this.props.children} 23 |
24 | ); 25 | } 26 | } 27 | 28 | App.propTypes = { 29 | /** 30 | * The child elements of the app. 31 | */ 32 | children: PropTypes.element, 33 | /** 34 | * A boolean indicating whether the websocket connection is loaded or not. 35 | */ 36 | webSocketLoading: PropTypes.bool, 37 | /** 38 | * A boolean indicating whether the puzzle is loaded or not. 39 | */ 40 | puzzleLoading: PropTypes.bool 41 | }; 42 | 43 | function mapStateToProps (state) { 44 | return { 45 | webSocketLoading: state.webSocket.loading, 46 | puzzleLoading: state.puzzle.loading 47 | }; 48 | } 49 | 50 | export default connect(mapStateToProps, null)(App); 51 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/igor.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8088 3 | address: 0.0.0.0 4 | 5 | jenkins: 6 | enabled: true 7 | masters: 8 | - name: jenkins 9 | address: http://jenkins.default:80 10 | username: jenkins 11 | password: jenkins 12 | 13 | travis: 14 | enabled: false 15 | masters: 16 | - name: ${services.travis.defaultMaster.name} 17 | baseUrl: ${services.travis.defaultMaster.baseUrl} 18 | address: ${services.travis.defaultMaster.address} 19 | githubToken: ${services.travis.defaultMaster.githubToken} 20 | 21 | dockerRegistry: 22 | enabled: false 23 | 24 | redis: 25 | connection: redis://redis:6379 26 | 27 | # Igor depends on Clouddriver and Echo. These are normally configured 28 | # in spinnaker[-local].yml (if present), otherwise, uncomment this. 29 | # services: 30 | clouddriver: 31 | baseUrl: http://clouddriver:7002 32 | echo: 33 | baseUrl: http://echo 34 | 35 | # spectator: 36 | # applicationName: ${spring.application.name} 37 | # webEndpoint: 38 | # enabled: ${services.spectator.webEndpoint.enabled:false} 39 | # prototypeFilter: 40 | # path: ${services.spectator.webEndpoint.prototypeFilter.path:} 41 | 42 | # stackdriver: 43 | # enabled: ${services.stackdriver.enabled} 44 | # projectName: ${services.stackdriver.projectName} 45 | # credentialsPath: ${services.stackdriver.credentialsPath} -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/run-apache2.sh: -------------------------------------------------------------------------------- 1 | # Set any missing env variables used to configure deck 2 | 3 | _DECK_HOST=0.0.0.0 4 | _DECK_PORT=9000 5 | _API_HOST=http://gate:8084 6 | _DECK_CERT_PATH=$DECK_CERT 7 | _DECK_KEY_PATH=$DECK_KEY 8 | 9 | if [ -z "$_DECK_HOST" ]; 10 | then 11 | _DECK_HOST=0.0.0.0 12 | fi 13 | 14 | if [ -z "$_DECK_PORT" ]; 15 | then 16 | _DECK_PORT=9000 17 | fi 18 | 19 | if [ -z "$_API_HOST" ]; 20 | then 21 | _API_HOST=http://localhost:8084 22 | fi 23 | 24 | if [ -z "$_DECK_CERT_PATH" ]; 25 | then 26 | # SSL not enabled 27 | cp docker/spinnaker.conf.gen spinnaker.conf 28 | else 29 | service apache2 stop 30 | a2enmod ssl 31 | cp docker/spinnaker.conf.ssl spinnaker.conf 32 | sed -ie 's|{%DECK_CERT_PATH%}|'$_DECK_CERT_PATH'|g' spinnaker.conf 33 | sed -ie 's|{%DECK_KEY_PATH%}|'$_DECK_KEY_PATH'|g' spinnaker.conf 34 | fi 35 | 36 | # Generate spinnaker.conf site & enable it 37 | 38 | sed -ie 's|{%DECK_HOST%}|'$_DECK_HOST'|g' spinnaker.conf 39 | sed -ie 's|{%DECK_PORT%}|'$_DECK_PORT'|g' spinnaker.conf 40 | sed -ie 's|{%API_HOST%}|'$_API_HOST'|g' spinnaker.conf 41 | 42 | mkdir -p /etc/apache2/sites-available 43 | mv spinnaker.conf /etc/apache2/sites-available 44 | 45 | a2ensite spinnaker 46 | 47 | # Update ports.conf to reflect desired deck host 48 | 49 | cp docker/ports.conf.gen ports.conf 50 | 51 | sed -ie "s/{%DECK_HOST%}/$_DECK_HOST/g" ports.conf 52 | sed -ie "s/{%DECK_PORT%}/$_DECK_PORT/g" ports.conf 53 | 54 | mv ports.conf /etc/apache2/ports.conf 55 | 56 | 57 | cp /settings.js /opt/deck/html/settings.js 58 | 59 | apache2ctl -D FOREGROUND -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/node 2 | 3 | YAML = require('yamljs'); 4 | var inquirer = require('inquirer'); 5 | var Rx = require('rx'); 6 | var prompts = new Rx.Subject(); 7 | 8 | const execSync = require('child_process').execSync; 9 | 10 | var ymlpath = process.argv[2]; 11 | 12 | 13 | inquirer.prompt(prompts).ui.process.subscribe( 14 | function(answers) { 15 | if(answers.hasOwnProperty('answer') && answers['answer'] === true){ 16 | execSync(answers.name, { 17 | stdio: [0, 1, 2], 18 | env: process.env 19 | }) 20 | } else { 21 | console.log('Skipping execution of: ', answers.name) 22 | } 23 | }, 24 | function(err) { 25 | console.log('error') 26 | }, 27 | function(message) { 28 | console.log('complete') 29 | } 30 | ); 31 | 32 | prompts.onNext({ 33 | type: 'confirm', 34 | name: ":", 35 | message: "Welcome to the Linux.com interactive Kubernetes tutorial by Kenzan. Press enter to begin\n", 36 | default: true 37 | }); 38 | 39 | YAML.load(ymlpath, function(docs) { 40 | var stepIndex = 1; 41 | docs.parts.forEach(function(item) { 42 | item.steps.forEach(function(step) { 43 | prompts.onNext({ 44 | type: 'confirm', 45 | name: step.com, 46 | message: "\n\n\n" + item.name + " Step: " + stepIndex++ + "\n" + step.cap + "\n\n" + step.com + "\n\nPress enter to run the above command for the step.", 47 | default: true 48 | }); 49 | }) 50 | }) 51 | prompts.onCompleted(); 52 | }); 53 | -------------------------------------------------------------------------------- /applications/monitor-scale/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: monitor-scale 5 | labels: 6 | app: monitor-scale 7 | spec: 8 | ports: 9 | - port: 3001 10 | targetPort: 3001 11 | selector: 12 | app: monitor-scale 13 | tier: monitor-scale 14 | type: NodePort 15 | 16 | --- 17 | 18 | apiVersion: extensions/v1beta1 19 | kind: Deployment 20 | metadata: 21 | name: monitor-scale 22 | labels: 23 | app: monitor-scale 24 | spec: 25 | strategy: 26 | type: Recreate 27 | template: 28 | metadata: 29 | labels: 30 | app: monitor-scale 31 | tier: monitor-scale 32 | spec: 33 | serviceAccountName: monitor-scale 34 | containers: 35 | - image: 127.0.0.1:30400/monitor-scale:$BUILD_TAG 36 | name: monitor-scale 37 | imagePullPolicy: Always 38 | ports: 39 | - containerPort: 3001 40 | name: monitor-scale 41 | - image: zappi/kubectl:latest 42 | name: kubectl-api 43 | args: ["proxy","-p","2345"] 44 | ports: 45 | - containerPort: 2345 46 | name: kubectl-api 47 | 48 | --- 49 | 50 | apiVersion: extensions/v1beta1 51 | kind: Ingress 52 | metadata: 53 | name: monitor-scale 54 | annotations: 55 | ingress.kubernetes.io/rewrite-target: / 56 | spec: 57 | backend: 58 | serviceName: monitor-scale 59 | servicePort: 3001 60 | rules: 61 | - host: monitor-scale.192.168.56.100.nip.io 62 | http: 63 | paths: 64 | - path: / 65 | backend: 66 | serviceName: monitor-scale 67 | servicePort: 3001 68 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/echo.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8089 3 | address: 0.0.0.0 4 | 5 | # cassandra: 6 | # enabled: true 7 | # embedded: true 8 | # # host: ${services.cassandra.host:localhost} 9 | 10 | spinnaker: 11 | baseUrl: http://localhost:9000 12 | cassandra: 13 | enabled: false 14 | 15 | inMemory: 16 | enabled: true 17 | 18 | 19 | 20 | front50: 21 | baseUrl: http://front50:8080 22 | 23 | orca: 24 | baseUrl: http://orca:8083 25 | 26 | endpoints.health.sensitive: false 27 | 28 | slack: 29 | enabled: false 30 | 31 | # spring: 32 | # mail: 33 | # host: ${mail.host} 34 | 35 | mail: 36 | enabled: false 37 | host: ${services.echo.notifications.mail.host} 38 | from: ${services.echo.notifications.mail.fromAddress} 39 | 40 | hipchat: 41 | enabled: false 42 | baseUrl: ${services.echo.notifications.hipchat.url} 43 | token: ${services.echo.notifications.hipchat.token} 44 | 45 | twilio: 46 | enabled: false 47 | baseUrl: ${services.echo.notifications.sms.url:https://api.twilio.com/} 48 | account: ${services.echo.notifications.sms.account} 49 | token: ${services.echo.notifications.sms.token} 50 | from: ${services.echo.notifications.sms.from} 51 | 52 | scheduler: 53 | compensationJob: 54 | enabled: true 55 | windowMs: 1800000 # optional 56 | 57 | spectator: 58 | applicationName: ${spring.application.name} 59 | webEndpoint: 60 | enabled: false 61 | prototypeFilter: 62 | path: ${services.spectator.webEndpoint.prototypeFilter.path:} 63 | 64 | stackdriver: 65 | enabled: false 66 | projectName: ${services.stackdriver.projectName} 67 | credentialsPath: ${services.stackdriver.credentialsPath} -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/shared/Slider.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ReactSlider from 'react-slider'; 4 | 5 | class SliderComponent extends React.Component { 6 | constructor (props) { 7 | super(props); 8 | 9 | this.renderSliderElement = this.renderSliderElement.bind(this); 10 | } 11 | componentDidMount () { 12 | this.renderSliderElement(); 13 | } 14 | 15 | componentDidUpdate () { 16 | this.renderSliderElement(); 17 | } 18 | 19 | renderSliderElement () { 20 | ReactDOM.render( 27 |
{this.props.properties.value}
28 |
, this.sliderRef); 29 | } 30 | 31 | render () { 32 | return ( 33 |
34 | {this.props.title && 35 |

{this.props.title}

} 36 |
{ this.sliderRef = sliderRef; }} 39 | /> 40 |
41 | ); 42 | } 43 | } 44 | 45 | SliderComponent.propTypes = { 46 | title: PropTypes.string, 47 | properties: PropTypes.shape({ 48 | min: PropTypes.number, 49 | max: PropTypes.number, 50 | step: PropTypes.number, 51 | defaultValue: PropTypes.number, 52 | value: PropTypes.number, 53 | onChange: PropTypes.func 54 | }).isRequired 55 | }; 56 | 57 | export default SliderComponent; 58 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/reducers/puzzleReducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/actionTypes'; 2 | 3 | const initialState = { 4 | fromCache: false, 5 | fromMongo: false, 6 | sendingData: false, 7 | puzzleId: '', 8 | puzzleData: [], 9 | loading: false 10 | }; 11 | 12 | export default function puzzleReducer (state = initialState, action) { 13 | switch (action.type) { 14 | case types.puzzle.PUZZLE_LOADING: { 15 | return Object.assign({}, state, { 16 | loading: true 17 | }); 18 | } 19 | case types.puzzle.GET_PUZZLE_DATA_SUCCESS: { 20 | return Object.assign({}, state, { 21 | puzzleData: action.data.words, 22 | loading: false 23 | }); 24 | } 25 | case types.puzzle.GET_PUZZLE_DATA_FAILURE: { 26 | return state; 27 | } 28 | case types.puzzle.SUBMIT_PUZZLE_DATA_SUCCESS: { 29 | return Object.assign({}, state, { 30 | puzzleData: action.data 31 | }); 32 | } 33 | case types.puzzle.SUBMIT_PUZZLE_DATA_FAILURE: { 34 | return state; 35 | } 36 | case types.puzzle.CLEAR_PUZZLE_DATA: { 37 | return Object.assign({}, state, { 38 | puzzleData: action.data 39 | }); 40 | } 41 | case types.puzzle.SENDING_DATA: { 42 | return Object.assign({}, state, { 43 | sendingData: action.data 44 | }); 45 | } 46 | case types.puzzle.FROM_MONGO: { 47 | return Object.assign({}, state, { 48 | fromMongo: action.data 49 | }); 50 | } 51 | case types.puzzle.FROM_CACHE: { 52 | return Object.assign({}, state, { 53 | fromCache: action.data 54 | }); 55 | } 56 | default: { 57 | return state; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/instance-grid/InstanceGrid.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classNames from 'classnames'; 3 | import Slider from '../shared/Slider'; 4 | 5 | function InstanceGrid (props) { 6 | const instances = props.pods.map((pod, index) => { 7 | const instanceClass = classNames({ 8 | instance: true, 9 | active: pod === props.activeInstance 10 | }); 11 | 12 | return
{pod}
; 13 | }); 14 | 15 | const sliderProps = Object.assign({}, props.properties, { 16 | value: props.instanceCount 17 | }); 18 | 19 | return ( 20 |
21 |
22 | {instances} 23 |
24 |
25 | 26 | 27 |
28 |
29 |

Choose the number of instances you want to scale in your cluster and click "Scale". 30 | This will call our service and scale up the number of replicas to the desired size.

31 |
32 |
33 | ); 34 | } 35 | 36 | InstanceGrid.propTypes = { 37 | properties: PropTypes.shape({ 38 | min: PropTypes.number, 39 | max: PropTypes.number, 40 | step: PropTypes.number, 41 | defaultValue: PropTypes.number, 42 | value: PropTypes.number, 43 | onChange: PropTypes.func, 44 | getSliderValue: PropTypes.func, 45 | onScale: PropTypes.func 46 | }), 47 | instanceCount: PropTypes.number, 48 | pods: PropTypes.array, 49 | activeInstance: PropTypes.string 50 | }; 51 | 52 | export default InstanceGrid; 53 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/home/HomePage.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | import classNames from 'classnames'; 5 | import * as actions from '../../actions/puzzleActions'; 6 | import PuzzleComponent from './PuzzleComponent'; 7 | import InstanceComponent from './InstancesComponent'; 8 | import DataFlowArrow from './DataFlowArrow'; 9 | import Database from './Database'; 10 | 11 | class HomePage extends React.Component { 12 | render () { 13 | const sendingDataClass = classNames({ 14 | 'data-flow': true, 15 | 'active': this.props.sendingData 16 | }); 17 | 18 | return ( 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 | ); 30 | } 31 | } 32 | 33 | HomePage.propTypes = { 34 | actions: PropTypes.objectOf(PropTypes.func), 35 | state: PropTypes.object, 36 | sendingData: PropTypes.bool, 37 | fromCache: PropTypes.bool, 38 | fromMongo: PropTypes.bool 39 | }; 40 | 41 | function mapStateToProps (state) { 42 | return { 43 | sendingData: state.puzzle.sendingData, 44 | fromCache: state.puzzle.fromCache, 45 | fromMongo: state.puzzle.fromMongo 46 | }; 47 | } 48 | 49 | function mapDispatchToProps (dispatch) { 50 | return { 51 | actions: bindActionCreators(actions, dispatch) 52 | }; 53 | } 54 | 55 | export default connect(mapStateToProps, mapDispatchToProps)(HomePage); 56 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/reducers/websocketReducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/actionTypes'; 2 | 3 | const initialState = { 4 | connected: false, 5 | pods: [], 6 | activePod: undefined, 7 | loading: false 8 | }; 9 | 10 | export default function websocketReducer (state = initialState, action) { 11 | switch (action.type) { 12 | case types.websocket.CONNECTION_LOADING: { 13 | return Object.assign({}, state, { 14 | loading: true 15 | }); 16 | } 17 | case types.websocket.CONNECT_TO_SOCKET: { 18 | return Object.assign({}, state, { 19 | connected: true, 20 | loading: false 21 | }); 22 | } 23 | case types.websocket.DISCONNECT_FROM_SOCKET: { 24 | return Object.assign({}, state, { 25 | connected: false 26 | }); 27 | } 28 | case types.websocket.GET_PODS: { 29 | return Object.assign({}, state, { 30 | pods: action.pods, 31 | loading: false 32 | }); 33 | } 34 | case types.websocket.POD_UP: { 35 | if (state.pods.includes(action.pod)) { 36 | return state; 37 | } else { 38 | const pods = [...state.pods, action.pod]; 39 | return Object.assign({}, state, { 40 | pods, 41 | loading: false 42 | }); 43 | } 44 | } 45 | case types.websocket.POD_DOWN: { 46 | const pods = state.pods.filter(pod => ( 47 | pod !== action.pod 48 | )); 49 | return Object.assign({}, state, { pods }); 50 | } 51 | case types.websocket.ACTIVE_INSTANCE: { 52 | return Object.assign({}, state, { activePod: action.activeInstance }); 53 | } 54 | case types.websocket.SCALE: { 55 | return state; 56 | } 57 | default: { 58 | return state; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /readme.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/node 2 | 3 | YAML = require('yamljs'); 4 | var fs = require('fs'); 5 | 6 | var markdown = "# Linux.com Kubernetes CI/CD Blog Series by Kenzan\n\n To generate this readme: `node readme.js`"; 7 | markdown = markdown + "\n\n## Interactive Tutorial Version" 8 | markdown = markdown + "\nTo complete the tutorial using the interactive script:\n" 9 | markdown = markdown + "\n* Clone this repository.\n" 10 | markdown = markdown + "\n* To ensure you are starting with a clean slate: `minikube delete; sudo rm -rf ~/.minikube; sudo rm -rf ~/.kube`\n" 11 | markdown = markdown + "\n* To run: `npm install`\n" 12 | markdown = markdown + "\nBegin the desired section:\n" 13 | markdown = markdown + "\n* `npm run part1`\n" 14 | markdown = markdown + "\n* `npm run part2`\n" 15 | markdown = markdown + "\n* `npm run part3`\n" 16 | markdown = markdown + "\n* `npm run part4`\n" 17 | markdown = markdown + "\n\n## Manual Tutorial Version\n\n" 18 | markdown = markdown + "\nTo complete the tutorial manually, follow the steps below.\n" 19 | 20 | 21 | YAML.load('steps.yml', function(docs) 22 | { 23 | docList = docs.parts; 24 | var parts = docs.parts; 25 | 26 | parts.forEach(function (item) { 27 | var part = item.name; 28 | var stepNum = 0; 29 | var stepList = item.steps; 30 | 31 | markdown = markdown + "\n\n## " + part; 32 | 33 | stepList.forEach(function (step) { 34 | stepNum++; 35 | markdown = markdown + "\n\n#### Step" + stepNum 36 | markdown = markdown + "\n\n" + step.cap 37 | markdown = markdown + "\n\n`" + step.com + "`" 38 | }) 39 | 40 | 41 | }) 42 | 43 | 44 | fs.writeFile("README.md", markdown, function(err) { 45 | if(err) { 46 | return console.log(err); 47 | } 48 | 49 | console.log("README.md saved!"); 50 | }); 51 | 52 | }); -------------------------------------------------------------------------------- /applications/jenkins/plugins.txt: -------------------------------------------------------------------------------- 1 | jdk-tool:1.1 2 | script-security:1.44 3 | command-launcher:1.2 4 | cloudbees-folder:6.5.1 5 | bouncycastle-api:2.16.3 6 | structs:1.14 7 | workflow-step-api:2.16 8 | scm-api:2.2.7 9 | workflow-api:2.29 10 | junit:1.24 11 | antisamy-markup-formatter:1.5 12 | token-macro:2.5 13 | build-timeout:1.19 14 | credentials:2.1.18 15 | ssh-credentials:1.14 16 | plain-credentials:1.4 17 | credentials-binding:1.16 18 | timestamper:1.8.10 19 | workflow-support:2.20 20 | durable-task:1.25 21 | workflow-durable-task-step:2.20 22 | matrix-project:1.13 23 | resource-disposer:0.12 24 | ws-cleanup:0.34 25 | ant:1.8 26 | gradle:1.29 27 | pipeline-milestone-step:1.3.1 28 | jquery-detached:1.2.1 29 | jackson2-api:2.8.11.3 30 | ace-editor:1.1 31 | workflow-scm-step:2.6 32 | workflow-cps:2.54 33 | pipeline-input-step:2.8 34 | pipeline-stage-step:2.3 35 | workflow-job:2.24 36 | pipeline-graph-analysis:1.7 37 | pipeline-rest-api:2.10 38 | handlebars:1.1.1 39 | momentjs:1.1.1 40 | pipeline-stage-view:2.10 41 | pipeline-build-step:2.7 42 | pipeline-model-api:1.3.1 43 | pipeline-model-extensions:1.3.1 44 | apache-httpcomponents-client-4-api:4.5.5-3.0 45 | jsch:0.1.54.2 46 | git-client:2.7.3 47 | git-server:1.7 48 | workflow-cps-global-lib:2.9 49 | display-url-api:2.2.0 50 | mailer:1.21 51 | branch-api:2.0.20 52 | workflow-multibranch:2.20 53 | authentication-tokens:1.3 54 | docker-commons:1.13 55 | workflow-basic-steps:2.9 56 | docker-workflow:1.17 57 | pipeline-stage-tags-metadata:1.3.1 58 | pipeline-model-declarative-agent:1.1.1 59 | pipeline-model-definition:1.3.1 60 | workflow-aggregator:2.5 61 | github-api:1.92 62 | git:3.9.1 63 | github:1.29.2 64 | github-branch-source:2.3.6 65 | pipeline-github-lib:1.0 66 | mapdb-api:1.0.9.0 67 | subversion:2.11.1 68 | ssh-slaves:1.26 69 | matrix-auth:2.3 70 | pam-auth:1.3 71 | ldap:1.20 72 | email-ext:2.63 73 | kubernetes-cd:0.2.3 74 | azure-commons:0.2.6 -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/templates/clouddriver.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | # namespace: spinnaker 5 | name: clouddriver 6 | labels: 7 | app: clouddriver 8 | spec: 9 | ports: 10 | - port: 7002 11 | targetPort: 7002 12 | selector: 13 | app: clouddriver 14 | tier: clouddriver 15 | type: NodePort 16 | 17 | --- 18 | apiVersion: extensions/v1beta1 19 | kind: Deployment 20 | metadata: 21 | # namespace: spinnaker 22 | name: clouddriver 23 | labels: 24 | app: clouddriver 25 | spec: 26 | strategy: 27 | type: Recreate 28 | template: 29 | metadata: 30 | labels: 31 | app: clouddriver 32 | tier: clouddriver 33 | spec: 34 | containers: 35 | - image: {{.Values.clouddriver.image}}:{{.Values.clouddriver.tag}} 36 | name: clouddriver 37 | 38 | ports: 39 | - containerPort: 7002 40 | name: clouddriver 41 | volumeMounts: 42 | - name: spinnaker-config 43 | mountPath: /opt/clouddriver/config 44 | - name: spinnaker-config 45 | mountPath: /root/.kube/config 46 | subPath: kube.config 47 | command: ["bash"] 48 | args: ["-c", "cd /usr/local/bin; curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl; chmod +x kubectl; /opt/clouddriver/bin/clouddriver"] 49 | 50 | - image: lachlanevenson/k8s-kubectl:latest 51 | name: kubectl 52 | ports: 53 | - containerPort: 8001 54 | name: kubectl 55 | args: ["proxy"] 56 | 57 | volumes: 58 | - name: spinnaker-config 59 | configMap: 60 | name: spinnaker-config 61 | # - name: kube-config 62 | # configMap: 63 | # name: spinnaker-config -------------------------------------------------------------------------------- /applications/puzzle/server/boot/puzzle.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "word": "Kenzan", 4 | "wordNbr": 7, 5 | "startx" : 6, 6 | "starty" : 10, 7 | "wordOrientation": "across", 8 | "hint": "Our company name :-)", 9 | "enteredValue": "******" 10 | }, 11 | { 12 | "word": "Minikube", 13 | "wordNbr": 1, 14 | "startx" : 9, 15 | "starty" : 0, 16 | "wordOrientation": "down", 17 | "hint": "Tool that makes it easy to run kubernetes locally", 18 | "enteredValue": "********" 19 | }, 20 | { 21 | "word": "Replication", 22 | "wordNbr": 2, 23 | "startx" : 11, 24 | "starty" : 0, 25 | "wordOrientation": "down", 26 | "hint": "Controller that ensures a specific number of pods are running at a given time", 27 | "enteredValue": "***********" 28 | }, 29 | { 30 | "word": "Service", 31 | "wordNbr": 3, 32 | "startx" : 5, 33 | "starty" : 1, 34 | "wordOrientation": "across", 35 | "hint": "Abstraction which defines a logical set of pods", 36 | "enteredValue": "*******" 37 | }, 38 | { 39 | "word": "Kubectl", 40 | "wordNbr": 4, 41 | "startx" : 3, 42 | "starty" : 4, 43 | "wordOrientation": "down", 44 | "hint": "cli tool for running commands against Kubernetes clusters", 45 | "enteredValue": "*******" 46 | }, 47 | { 48 | "word": "Deployment", 49 | "wordNbr": 5, 50 | "startx" : 2, 51 | "starty" : 7, 52 | "wordOrientation": "across", 53 | "hint": "Defines declarative updates for pods and replica sets", 54 | "enteredValue": "**********" 55 | }, 56 | { 57 | "word": "Yaml", 58 | "wordNbr": 6, 59 | "startx" : 0, 60 | "starty" : 10, 61 | "wordOrientation": "across", 62 | "hint": "Configuration format other than json", 63 | "enteredValue": "****" 64 | }, 65 | { 66 | "word": "k8s", 67 | "wordNbr": 4, 68 | "startx" : 3, 69 | "starty" : 4, 70 | "wordOrientation": "across", 71 | "hint": "Shorthand for Kubernetes", 72 | "enteredValue": "***" 73 | }, 74 | { 75 | "word": "cncf", 76 | "wordNbr": 8, 77 | "startx" : 8, 78 | "starty" : 9, 79 | "wordOrientation": "down", 80 | "hint": "Organization that manages Kubernetes certifications", 81 | "enteredValue": "****" 82 | } 83 | ] 84 | -------------------------------------------------------------------------------- /applications/puzzle/k8s/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: puzzle 5 | labels: 6 | app: puzzle 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: puzzle 11 | strategy: 12 | type: Recreate 13 | template: 14 | metadata: 15 | labels: 16 | app: puzzle 17 | tier: puzzle 18 | spec: 19 | containers: 20 | - image: 127.0.0.1:30400/puzzle:$BUILD_TAG 21 | name: puzzle 22 | imagePullPolicy: Always 23 | lifecycle: 24 | postStart: 25 | exec: 26 | command: ["/up.sh"] 27 | preStop: 28 | exec: 29 | command: ["/down.sh"] 30 | ports: 31 | - containerPort: 3000 32 | name: puzzle 33 | 34 | --- 35 | apiVersion: v1 36 | kind: Service 37 | metadata: 38 | name: puzzle 39 | labels: 40 | app: puzzle 41 | spec: 42 | ports: 43 | - port: 3000 44 | targetPort: 3000 45 | selector: 46 | app: puzzle 47 | tier: puzzle 48 | type: NodePort 49 | --- 50 | apiVersion: apps/v1 51 | kind: Deployment 52 | metadata: 53 | name: mongo 54 | labels: 55 | app: mongo 56 | spec: 57 | selector: 58 | matchLabels: 59 | app: mongo 60 | strategy: 61 | type: Recreate 62 | template: 63 | metadata: 64 | labels: 65 | app: mongo 66 | tier: mongo 67 | spec: 68 | containers: 69 | - image: mongo:5.0.11 70 | name: mongo 71 | imagePullPolicy: Always 72 | ports: 73 | - containerPort: 27017 74 | name: mongo 75 | --- 76 | apiVersion: v1 77 | kind: Service 78 | metadata: 79 | name: mongo 80 | labels: 81 | app: mongo 82 | spec: 83 | ports: 84 | - port: 27017 85 | targetPort: 27017 86 | selector: 87 | app: mongo 88 | tier: mongo 89 | type: NodePort 90 | --- 91 | apiVersion: networking.k8s.io/v1 92 | kind: Ingress 93 | metadata: 94 | name: puzzle 95 | spec: 96 | backend: 97 | serviceName: puzzle 98 | servicePort: 3000 99 | rules: 100 | - host: puzzle.192.168.56.100.nip.io 101 | http: 102 | paths: 103 | - path: / 104 | backend: 105 | serviceName: puzzle 106 | servicePort: 3000 107 | -------------------------------------------------------------------------------- /manifests/registry.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolume 2 | apiVersion: v1 3 | metadata: 4 | name: registry 5 | labels: 6 | type: local 7 | spec: 8 | capacity: 9 | storage: 4Gi 10 | accessModes: 11 | - ReadWriteOnce 12 | hostPath: 13 | path: "/data/registry/" 14 | 15 | --- 16 | kind: PersistentVolumeClaim 17 | apiVersion: v1 18 | metadata: 19 | name: registry-claim 20 | spec: 21 | accessModes: 22 | - ReadWriteOnce 23 | resources: 24 | requests: 25 | storage: 4Gi 26 | --- 27 | 28 | apiVersion: v1 29 | kind: Service 30 | metadata: 31 | name: registry 32 | labels: 33 | app: registry 34 | spec: 35 | ports: 36 | - port: 5000 37 | targetPort: 5000 38 | nodePort: 30400 39 | name: registry 40 | selector: 41 | app: registry 42 | tier: registry 43 | type: NodePort 44 | --- 45 | 46 | apiVersion: v1 47 | kind: Service 48 | metadata: 49 | name: registry-ui 50 | labels: 51 | app: registry 52 | spec: 53 | ports: 54 | - port: 8080 55 | targetPort: 8080 56 | name: registry 57 | selector: 58 | app: registry 59 | tier: registry 60 | type: NodePort 61 | --- 62 | 63 | apiVersion: extensions/v1beta1 64 | kind: Deployment 65 | metadata: 66 | name: registry 67 | labels: 68 | app: registry 69 | spec: 70 | strategy: 71 | type: Recreate 72 | template: 73 | metadata: 74 | labels: 75 | app: registry 76 | tier: registry 77 | spec: 78 | containers: 79 | - image: registry:2 80 | name: registry 81 | volumeMounts: 82 | - name: docker 83 | mountPath: /var/run/docker.sock 84 | - name: registry-persistent-storage 85 | mountPath: /var/lib/registry 86 | ports: 87 | - containerPort: 5000 88 | name: registry 89 | - name: registryui 90 | image: hyper/docker-registry-web:latest 91 | ports: 92 | - containerPort: 8080 93 | env: 94 | - name: REGISTRY_URL 95 | value: http://localhost:5000/v2 96 | - name: REGISTRY_NAME 97 | value: cluster-registry 98 | volumes: 99 | - name: docker 100 | hostPath: 101 | path: /var/run/docker.sock 102 | - name: registry-persistent-storage 103 | persistentVolumeClaim: 104 | claimName: registry-claim -------------------------------------------------------------------------------- /scripts/kubescale.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # echo "creating pod-list etcd directory" 4 | # kubectl exec -it example-etcd-cluster-0000 apk update 5 | # kubectl exec -it example-etcd-cluster-0000 apk add ca-certificates 6 | # kubectl exec -it example-etcd-cluster-0000 apk update-ca-certificates 7 | # kubectl exec -it example-etcd-cluster-0000 apk add bash wget 8 | # kubectl exec -it example-etcd-cluster-0000 wget https://gist.githubusercontent.com/moondev/86ebfc39998049d3f0c10848f4c72c57/raw/62cb237a115d4884cec4c5d94751cf5586f44b4b/mkdir.sh 9 | # kubectl exec -it example-etcd-cluster-0000 chmod +x mkdir.sh 10 | 11 | 12 | kubectl exec -it example-etcd-cluster-0000 echo "#!/usr/bin/env bash\n\nexport ETCDCTL_ENDPOINT='http://example-etcd-cluster-client-service:2379'\nectdctl mkdir pod-list" > /mkd.sh 13 | kubectl exec -it example-etcd-cluster-0000 chmod 0777 /mkd.sh 14 | 15 | kubectl exec -it example-etcd-cluster-0000 cat /mkd.sh 16 | 17 | # echo "building kubescale image" 18 | 19 | # TAG=latest 20 | 21 | # docker build -t 127.0.0.1:30400/kubescale:$TAG -f 22 | 23 | # # echo "building set image" 24 | 25 | # cd set; docker build -t 127.0.0.1:30400/set:$TAG -f set/Dockerfile . 26 | 27 | 28 | # echo "forwarding registry port" 29 | 30 | # export MINIKUBEIP=`minikube ip` 31 | 32 | # #temp container for forwarding to registry 33 | # docker run -d -e "REG_IP=`minikube ip`" -e "REG_PORT=30400" --name socat-registry -p 30400:5000 socat-registry 34 | 35 | # sleep 5 36 | 37 | # echo "pushing kubescale image" 38 | # docker push 127.0.0.1:30400/kubescale:latest 39 | # docker push 127.0.0.1:30400/set:latest 40 | 41 | # echo "pushing set image" 42 | 43 | # docker push 127.0.0.1:30400/set:latest 44 | 45 | # sleep 2 46 | # echo "killing port-forward" 47 | 48 | # docker stop socat-registry 49 | # docker rm socat-registry 50 | 51 | # echo "deploying kubescale and set" 52 | 53 | # kubectl apply -f k8s/kubescale.yml 54 | # kubectl rollout status deployment/kubescale 55 | 56 | # kubectl apply -f k8s/set.yml 57 | # kubectl rollout status deployment/set 58 | 59 | 60 | # #temp container for forwarding to registry 61 | 62 | 63 | # docker stop socat-minikube 64 | # docker rm socat-minikube 65 | 66 | # proxy container for ingress 67 | 68 | # docker run -d -e "REG_IP=`minikube ip`" -e "REG_PORT=30400" --name socat-registry -p 30400:5000 socat-registry 69 | 70 | # # kubectl apply -f k8s/ing.yml 71 | 72 | # kubectl apply -f k8s/jenkins.yml 73 | # kubectl rollout status deployments/jenkins 74 | 75 | # sleep 10 76 | 77 | # open http://$MINIKUBEIP:31980 || true 78 | # xdg-open http://$MINIKUBEIP:31980 || true -------------------------------------------------------------------------------- /part4.yml: -------------------------------------------------------------------------------- 1 | parts: 2 | 3 | - name: Part 4 4 | intro: In this part we will return to our Jenkins instance and setup a pipeline for the kr8sswordz application. 5 | steps: 6 | 7 | - cap: Enter the following command to open the Jenkins UI in a web browser. Log in to Jenkins using the username and password you previously set up. 8 | com: minikube service jenkins 9 | 10 | - cap: We’ll want to create a new pipeline for the puzzle service that we previously deployed. On the left in Jenkins, click New Item. 11 | com: echo '' 12 | 13 | - cap: Enter the item name as "Puzzle-Service", click Pipeline, and click OK. 14 | com: echo '' 15 | 16 | - cap: Under the Build Triggers section, select Poll SCM. For the Schedule, enter the the string H/5 * * * * which will poll the Git repo every 5 minutes for changes. 17 | com: echo '' 18 | 19 | - cap: In the Pipeline section, change the Definition to "Pipeline script from SCM". Set the SCM property to GIT. Set the Repository URL to your forked repo (created in Part 2), such as https://github.com/[GIT USERNAME]/kubernetes-ci-cd.git. Set the Script Path to applications/puzzle/Jenkinsfile 20 | com: echo '' 21 | 22 | - cap: When you are finished, click Save. On the left, click Build Now to run the new pipeline. This will rebuild the image from the registry, and redeploy the puzzle pod. You should see it successfully run through the build, push, and deploy steps in a few minutes. 23 | com: echo '' 24 | 25 | - cap: View the Kr8sswordz application. 26 | com: minikube service kr8sswordz 27 | 28 | - cap: Spin up several instances of the puzzle service by moving the slider to the right and clicking Scale. For reference, click on the Submit button, noting that the white hit does not register on the puzzle services. 29 | com: echo '' 30 | 31 | - cap: Edit applications/puzzle/common/models/crossword.js in your favorite text editor (for example, you can use nano by running the command 'nano applications/puzzle/common/models/crossword.js' in a separate terminal). You'll see a commented section on lines 42-43 that indicates to uncomment a specific line. Uncomment line 43 by deleting the forward slashes and save the file. 32 | com: echo '' 33 | 34 | - cap: Commit and push the change to your forked Git repo. 35 | com: echo '' 36 | 37 | - cap: In Jenkins, open up the Puzzle-Service pipeline and wait until it triggers a build. It should trigger every 5 minutes. 38 | com: echo '' 39 | 40 | - cap: After it triggers, observe how the puzzle services disappear in the Kr8sswordz Puzzle app, and how new ones take their place. 41 | com: echo '' 42 | 43 | - cap: Try clicking Submit to test that hits now register as white. 44 | com: echo '' 45 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/clouddriver.yml: -------------------------------------------------------------------------------- 1 | aws: 2 | enabled: false 3 | defaults: 4 | iamRole: BaseIAMRole 5 | defaultRegions: 6 | - name: us-east-1 7 | defaultFront50Template: http://front50-service:8080 8 | defaultKeyPairTemplate: keypair 9 | migration: 10 | infrastructureApplications: null 11 | # an empty list means we are directly managing the AWS account we have credentials for (named default.account.env) 12 | # see prod profile section below for an example configuration to manage other accounts via STS assume role 13 | accounts: [] 14 | 15 | 16 | azure: 17 | enabled: false 18 | cf: 19 | accounts: 20 | enabled: false 21 | credentials: 22 | challengeDestructiveActionsEnvironments: k8s 23 | primaryAccountTypes: k8s 24 | default: 25 | account: 26 | env: k8s 27 | 28 | kubernetes: 29 | enabled: true 30 | accounts: 31 | - name: k8s 32 | # kubeconfigFile: /root/kube.config 33 | namespaces: 34 | - default 35 | - spinnaker 36 | # user: # optional user to authenticate as that must exist in the provided kube config file 37 | # cluster: # optional cluster to connect to that must exist in the provided kube config file 38 | dockerRegistries: 39 | - accountName: quay 40 | # namespaces: # optional list of namespaces this docker registry can deploy to 41 | 42 | dockerRegistry: 43 | enabled: true 44 | accounts: 45 | - name: quay 46 | address: https://quay.io 47 | username: # optional username for authenticating with the registry 48 | password: # optional password for authenticating with the registry 49 | email: # optional email for authenticating with the registry 50 | repositories: 51 | - spinnaker/deck 52 | - spinnaker/gate 53 | - spinnaker/orca 54 | - spinnaker/echo 55 | - spinnaker/igor 56 | - spinnaker/front50 57 | - spinnaker/clouddriver 58 | # - moondev/nginx 59 | # cacheThreads: # optional (default is 1) number of threads to cache registry contents across 60 | # clientTimeoutMillis: # optional (default is 1 minute) time before the registry connection times out 61 | # paginateSize: # optional (default is 100) number of entries to request from /_catalog at a time 62 | 63 | # repositories: 64 | # - nginx 65 | # cacheThreads: # optional (default is 1) number of threads to cache registry contents across 66 | # clientTimeoutMillis: # optional (default is 1 minute) time before the registry connection times out 67 | # paginateSize: # optional (default is 100) number of entries to request from /_catalog at a time 68 | 69 | 70 | google: 71 | enabled: false 72 | 73 | redis: 74 | enabled: true 75 | connection: redis://redis:6379 76 | scheduler: default 77 | parallelism: -1 78 | server: 79 | port: 7002 80 | address: 0.0.0.0 81 | services: 82 | front50: 83 | baseUrl: http://front50:8080 84 | udf: 85 | enabled: false -------------------------------------------------------------------------------- /applications/puzzle/common/models/crossword.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Crossword", 3 | "base": "PersistedModel", 4 | "options": { 5 | "validateUpsert": true 6 | }, 7 | "properties": { 8 | "fromCache": { 9 | "type": "boolean" 10 | }, 11 | "words": { 12 | "type": [ 13 | { 14 | "word": { 15 | "type": "string", 16 | "description": "The correct answer to the puzzle location", 17 | "trim": true, 18 | "required": true 19 | }, 20 | "wordNbr": { 21 | "type": "number", 22 | "description": "The ID of the word the word", 23 | "required": true 24 | }, 25 | "startx": { 26 | "type": "number", 27 | "description": "The x coordinates of the starting word", 28 | "required": true 29 | }, 30 | "starty": { 31 | "type": "number", 32 | "description": "the y coordinate of the starting word", 33 | "required": true 34 | }, 35 | "wordOrientation": { 36 | "type": "string", 37 | "description": "The crossword puzzle direction of word", 38 | "required": true 39 | }, 40 | "hint": { 41 | "type": "string", 42 | "description": "A hint to solve the word", 43 | "required": true 44 | }, 45 | "enteredValue": { 46 | "type": "string", 47 | "description": "The value that the user entered in the puzzle", 48 | "required": false 49 | } 50 | } 51 | ] 52 | } 53 | }, 54 | "validations": [], 55 | "relations": {}, 56 | "acls": [], 57 | "methods": { 58 | "get": { 59 | "accepts": [], 60 | "returns": { 61 | "type": "object", 62 | "root": true 63 | }, 64 | "description": "This returns the crossword puzzle", 65 | "http": [ 66 | { 67 | "path": "/", 68 | "verb": "get" 69 | } 70 | ] 71 | }, 72 | "put": { 73 | "accepts": [ 74 | { 75 | "arg": "words", 76 | "type": "array", 77 | "http": { 78 | "source": "body" 79 | }, 80 | "default": "[{\"wordNbr\": 6,\"wordOrientation\": \"across\",\"enteredValue\": \"deploy****\"},{\"wordNbr\": 7,\"wordOrientation\": \"across\",\"enteredValue\": \"yaml\"}]" 81 | } 82 | ], 83 | "returns": [], 84 | "description": "This updates the backend data store with new values updated by the user", 85 | "http": [ 86 | { 87 | "path": "/", 88 | "verb": "put" 89 | } 90 | ] 91 | }, 92 | "clear": { 93 | "accepts": [], 94 | "returns": [], 95 | "description": "This clears the any values entered by the user from the backend. It will remove any values of 'enteredValue' from the database.", 96 | "http": [ 97 | { 98 | "path": "/clear", 99 | "verb": "put" 100 | } 101 | ] 102 | } 103 | }, 104 | "http": { 105 | "path": "/crossword" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const gulp = require('gulp'); 4 | const del = require('del'); 5 | const webpack = require('webpack-stream'); 6 | const sourcemaps = require('gulp-sourcemaps'); 7 | const sass = require('gulp-sass'); 8 | const browerSync = require('browser-sync'); 9 | const connect = require('gulp-connect'); 10 | const historyApiFallback = require('connect-history-api-fallback'); 11 | const runSequence = require('run-sequence'); 12 | const plumber = require('gulp-plumber'); 13 | const imageMin = require('gulp-imagemin'); 14 | const autoprefixer = require('autoprefixer'); 15 | const postcss = require('gulp-postcss'); 16 | const fs = require('fs'); 17 | 18 | const src = './src/'; 19 | const dist = './dist/'; 20 | 21 | gulp.task('clean:dist', (done) => { 22 | del(dist).then(() => { 23 | done(); 24 | }); 25 | }); 26 | 27 | gulp.task('compile:js', () => { 28 | return gulp.src(src + 'index.js') 29 | .pipe(plumber()) 30 | .pipe(webpack(require('./webpack.config.js'))) 31 | .pipe(gulp.dest(dist)); 32 | }); 33 | 34 | gulp.task('compile:sass', () => { 35 | return gulp.src(src + '**/*.scss') 36 | .pipe(sourcemaps.init()) 37 | .pipe(sass({includePaths: ['node_modules/susy/sass']}).on('error', sass.logError)) 38 | .pipe(postcss([ autoprefixer({ browsers: ['> 5%', 'iOS 7'] }) ])) 39 | .pipe(sourcemaps.write()) 40 | .pipe(gulp.dest(dist)); 41 | }); 42 | 43 | gulp.task('copy:html', () => { 44 | return gulp.src(src + '**/*.html') 45 | .pipe(gulp.dest(dist)); 46 | }); 47 | 48 | gulp.task('copy:images', () => { 49 | return gulp.src([src + 'assets/**/*.jpg', src + 'assets/**/*.png']) 50 | .pipe(imageMin()) 51 | .pipe(gulp.dest(dist + 'assets/')); 52 | }); 53 | 54 | gulp.task('copy:assets', () => { 55 | return gulp.src([src + 'assets/**/*.*', !src + 'assets/**/*.jpg', !src + 'assets/**/*.png']) 56 | .pipe(gulp.dest(dist + 'assets/')); 57 | }); 58 | 59 | gulp.task('watch:html', () => { 60 | gulp.watch(src + '**/*.html', ['copy:html']); 61 | }); 62 | 63 | gulp.task('watch:sass', () => { 64 | gulp.watch([src + 'styles/**/*.scss', src + 'style.scss'], ['compile:sass']); 65 | }); 66 | 67 | gulp.task('watch:js', () => { 68 | gulp.watch([src + '**/*.js', '!' + src + '**/*.test.js'], ['compile:js']); 69 | }); 70 | 71 | gulp.task('build:dev', (done) => { 72 | runSequence('clean:dist', 73 | ['compile:js', 'compile:sass', 'copy:html', 'copy:images', 'copy:assets'], 74 | done); 75 | }); 76 | 77 | 78 | // Development Build 79 | gulp.task('serve:dev', ['build:dev', 'watch:html', 'watch:sass', 'watch:js'], () => { 80 | browerSync.init({ 81 | files: ['dist/**/*.js', 'dist/**/*.html', 'dist/**/*.css'], 82 | server: { 83 | baseDir: ['./dist/', './'], 84 | middleware: [historyApiFallback()] 85 | }, 86 | logLevel: 'info', 87 | port: 3002 88 | }); 89 | 90 | }); 91 | 92 | // Production Build 93 | gulp.task('serve:prod', ['build:dev'], () => { 94 | connect.server({ 95 | root: ['./dist/', './'], 96 | port: 3002, 97 | livereload: false, 98 | middleware: (connect, opt) => { 99 | return [ historyApiFallback() ]; 100 | } 101 | }); 102 | 103 | }); 104 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/actions/webSocketActions.js: -------------------------------------------------------------------------------- 1 | import io from 'socket.io-client'; 2 | import constants from '../constants'; 3 | import * as types from './actionTypes'; 4 | 5 | const baseUrl = `http://monitor-scale.${constants.minikubeIp}.nip.io`; 6 | const socket = io(baseUrl); 7 | 8 | export function getPods () { 9 | return dispatch => { 10 | return fetch(`${baseUrl}/pods`) 11 | .then(resp => ( 12 | resp.json() 13 | )) 14 | .then(json => { 15 | const pods = json.pods.map(pod => ( 16 | concatServiceName(pod.key) 17 | )); 18 | dispatch({type: types.websocket.GET_PODS, pods}); 19 | }) 20 | .catch(err => { 21 | throw err; 22 | }); 23 | }; 24 | } 25 | 26 | export function connectToSocket () { 27 | return dispatch => { 28 | dispatch({type: types.websocket.CONNECTION_LOADING}); 29 | socket.open(() => { 30 | dispatch({type: 'CONNECT_TO_SOCKET'}); 31 | }); 32 | socket.on('pods', (data) => { 33 | const pod = concatServiceName(data.pods); 34 | 35 | if (data.action === 'set') { 36 | dispatch({ type: types.websocket.POD_UP, pod }); 37 | } else if (data.action === 'delete') { 38 | dispatch({ type: types.websocket.POD_DOWN, pod }); 39 | } 40 | }); 41 | socket.on('hit', (data) => { 42 | const activeInstance = concatServiceName(data.podId); 43 | dispatch({ type: types.websocket.ACTIVE_INSTANCE, activeInstance }); 44 | }); 45 | }; 46 | } 47 | 48 | export function disconnectFromSocket () { 49 | return dispatch => { 50 | socket.close(() => { 51 | dispatch({type: 'DISCONNECT_FROM_SOCKET'}); 52 | }); 53 | }; 54 | } 55 | 56 | export function scale (data) { 57 | return dispatch => { 58 | const submission = JSON.stringify({'count': data}); 59 | const headers = new Headers(); 60 | headers.append('Content-Type', 'application/json'); 61 | 62 | return fetch(`${baseUrl}/scale`, { 63 | method: 'POST', 64 | headers, 65 | body: submission 66 | }); 67 | }; 68 | } 69 | 70 | export function submitConcurrentRequests (count) { 71 | return dispatch => { 72 | const submission = JSON.stringify({'count': count}); 73 | const headers = new Headers(); 74 | headers.append('Content-Type', 'application/json'); 75 | 76 | return fetch(`${baseUrl}/loadtest/concurrent`, { 77 | method: 'POST', 78 | headers, 79 | body: submission 80 | }) 81 | .catch(err => { 82 | throw (err); 83 | }); 84 | }; 85 | } 86 | 87 | export function submitConsecutiveRequests (count) { 88 | return dispatch => { 89 | const submission = JSON.stringify({'count': count}); 90 | const headers = new Headers(); 91 | headers.append('Content-Type', 'application/json'); 92 | 93 | return fetch(`${baseUrl}/loadtest/consecutive`, { 94 | method: 'POST', 95 | headers, 96 | body: submission 97 | }) 98 | .catch(err => { 99 | throw (err); 100 | }); 101 | }; 102 | } 103 | 104 | function concatServiceName (name) { 105 | const parts = name.split('/'); 106 | const serviceName = parts[parts.length - 1]; 107 | return serviceName; 108 | } 109 | -------------------------------------------------------------------------------- /applications/puzzle/common/models/crossword.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var request = require('request'); 3 | var Etcd = require('node-etcd') 4 | 5 | module.exports = function(Crossword) { 6 | 7 | var etcd = new Etcd("http://example-etcd-cluster-client-service:2379"); 8 | fireHit(); 9 | Crossword.get = function(cb) { 10 | 11 | var etcdPuzzleResp = etcd.getSync("puzzle"); 12 | 13 | if (etcdPuzzleResp && !etcdPuzzleResp.err) { 14 | 15 | console.log(`Responding with cache`); 16 | fireHit(); 17 | var cachedPuzzle = JSON.parse(etcdPuzzleResp.body.node.value); 18 | cachedPuzzle.fromCache = true; 19 | cb(null, cachedPuzzle); 20 | } else { 21 | Crossword.findOne(function(err, crossword) { 22 | 23 | fireHit(); 24 | if(err) { 25 | handleError(err.message, cb); 26 | } else { 27 | var puzzleString = JSON.stringify(crossword); 28 | etcd.setSync("puzzle", puzzleString, { ttl: 30 }); 29 | console.log(`Responding from Mongo`); 30 | crossword.fromCache = false; 31 | cb(null, crossword); 32 | } 33 | }); 34 | } 35 | } 36 | 37 | Crossword.put = function(words, cb) { 38 | if(words) { 39 | etcd.delSync("puzzle"); 40 | Crossword.findOne(function (err, crossword) { 41 | 42 | // Part 4: Uncomment the next line to enable puzzle pod highlighting when clicking the Submit button 43 | fireHit(); 44 | if (err) handleError(err.message, cb); 45 | for (var j = 0; j < words.length; j++) { 46 | var word = words[j]; 47 | var updatedWords = []; 48 | for (var i = 0; i < crossword.words.length; i++) { 49 | var crosswordWord = crossword.words[i]; 50 | if (crosswordWord.wordNbr === word.wordNbr && crosswordWord.wordOrientation === word.wordOrientation) { 51 | crosswordWord.enteredValue = word.enteredValue; 52 | //crosswordWord.wordOrientation = word.wordOrientation; 53 | } 54 | updatedWords.push(crosswordWord); 55 | } 56 | } 57 | crossword.updateAttribute('words', updatedWords, function (err, crossword) { 58 | if (err) handleError(err.message, cb); 59 | cb(null); 60 | }); 61 | }); 62 | } 63 | else handleError('word array is required', cb, 400) 64 | } 65 | 66 | Crossword.clear = function(cb) { 67 | 68 | Crossword.findOne(function(err, crossword) { 69 | console.log("Calling hit from clear."); 70 | fireHit(); 71 | if(err) handleError(err.message, cb); 72 | var updatedWords = []; 73 | for (var i = 0; i < crossword.words.length; i++) { 74 | var crosswordWord = crossword.words[i]; 75 | crosswordWord.enteredValue = undefined; 76 | updatedWords.push(crosswordWord); 77 | } 78 | crossword.updateAttribute('words', updatedWords, function(err, crossword) { 79 | if(err) handleError(err.message, cb); 80 | cb(null); 81 | }); 82 | }); 83 | } 84 | 85 | function fireHit() { 86 | var podId = process.env.HOSTNAME; 87 | var url = "http://monitor-scale:3001/hit/" + podId; 88 | request(url); 89 | } 90 | 91 | function handleError(msg, cb, status) { 92 | var error = new Error(msg); 93 | error.status = status | 500; 94 | return cb(error); 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/utils/_typography.scss: -------------------------------------------------------------------------------- 1 | @import 'https://fonts.googleapis.com/css?family=Open+Sans:300,400,700,800'; 2 | @import './variables'; 3 | 4 | // Roboto Font 5 | @font-face { 6 | font-family: 'Roboto'; 7 | src: url('../../assets/fonts/Roboto-Regular-webfont.eot'); 8 | src: url('../../assets/fonts/Roboto-Regular-webfont.eot?#iefix') format('embedded-opentype'), 9 | url('../../assets/fonts/Roboto-Regular-webfont.woff') format('woff'), 10 | url('../../assets/fonts/Roboto-Regular-webfont.ttf') format('truetype'), 11 | url('../../assets/fonts/Roboto-Regular-webfont.svg') format('svg'); 12 | font-weight: 400; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Roboto'; 17 | src: url('../../assets/fonts/Roboto-Light-webfont.eot'); 18 | src: url('../../assets/fonts/Roboto-Light-webfont.eot?#iefix') format('embedded-opentype'), 19 | url('../../assets/fonts/Roboto-Light-webfont.woff') format('woff'), 20 | url('../../assets/fonts/Roboto-Light-webfont.ttf') format('truetype'), 21 | url('../../assets/fonts/Roboto-Light-webfont.svg') format('svg'); 22 | font-weight: 200; 23 | } 24 | 25 | @font-face { 26 | font-family: 'Roboto'; 27 | src: url('../../assets/fonts/Roboto-LightItalic-webfont.eot'); 28 | src: url('../../assets/fonts/Roboto-LightItalic-webfont.eot?#iefix') format('embedded-opentype'), 29 | url('../../assets/fonts/Roboto-LightItalic-webfont.woff') format('woff'), 30 | url('../../assets/fonts/Roboto-LightItalic-webfont.ttf') format('truetype'), 31 | url('../../assets/fonts/Roboto-LightItalic-webfont.svg') format('svg'); 32 | font-weight: 200; 33 | font-style: italic; 34 | } 35 | 36 | @font-face { 37 | font-family: 'Roboto'; 38 | src: url('../../assets/fonts/Roboto-Bold-webfont.eot'); 39 | src: url('../../assets/fonts/Roboto-Bold-webfont.eot?#iefix') format('embedded-opentype'), 40 | url('../../assets/fonts/Roboto-Bold-webfont.woff') format('woff'), 41 | url('../../assets/fonts/Roboto-Bold-webfont.ttf') format('truetype'), 42 | url('../../assets/fonts/Roboto-Bold-webfont.svg') format('svg'); 43 | font-weight: 600; 44 | } 45 | 46 | @font-face { 47 | font-family: 'Roboto'; 48 | src: url('../../assets/fonts/Roboto-BoldItalic-webfont.eot'); 49 | src: url('../../assets/fonts/Roboto-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), 50 | url('../../assets/fonts/Roboto-BoldItalic-webfont.woff') format('woff'), 51 | url('../../assets/fonts/Roboto-BoldItalic-webfont.ttf') format('truetype'), 52 | url('../../assets/fonts/Roboto-BoldItalic-webfont.svg') format('svg'); 53 | font-weight: 600; 54 | font-style: italic; 55 | } 56 | 57 | @import './variables'; 58 | 59 | h1, 60 | h2, 61 | h3, 62 | h4, 63 | h5, 64 | h6 { 65 | font-weight: 300; 66 | line-height: 1.5; 67 | margin-top: 0; 68 | color: $brand-blue; 69 | 70 | &.bold { 71 | font-weight: 400; 72 | } 73 | 74 | &.inline { 75 | display: inline; 76 | } 77 | } 78 | 79 | // Header loop vars 80 | $max-header-font-size: 2rem; 81 | $header-count: 6; 82 | 83 | // Loop through h tags to assign relational font size and bottom margin 84 | @for $i from 1 through $header-count { 85 | $header-font-size: $max-header-font-size - ( ($i - 1) / $header-count); 86 | h#{$i}, 87 | .h#{$i} { 88 | font-size: $header-font-size; 89 | margin-bottom: $header-font-size * .667; 90 | } 91 | } 92 | 93 | p { 94 | line-height: 1.5; 95 | font-size: 0.9rem; 96 | color: $brand-blue; 97 | 98 | &.bold { 99 | font-weight: 800; 100 | } 101 | 102 | &.inline { 103 | display: inline; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/home/InstancesComponent.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | import Slider from '../shared/Slider'; 5 | import * as actions from '../../actions/webSocketActions'; 6 | import InstanceGrid from '../instance-grid/InstanceGrid'; 7 | import { instanceConfig } from './instanceConfig'; 8 | 9 | class InstancesComponent extends React.Component { 10 | constructor (props) { 11 | super(props); 12 | 13 | this.state = { 14 | scaleCount: props.pods.length, 15 | consecutiveRequestCount: 10 16 | }; 17 | 18 | this.handleScaleSlider = this.handleScaleSlider.bind(this); 19 | this.onScale = this.onScale.bind(this); 20 | this.handleConsecutiveRequestSlider = this.handleConsecutiveRequestSlider.bind(this); 21 | this.submitConsecutiveRequests = this.submitConsecutiveRequests.bind(this); 22 | } 23 | 24 | componentWillMount () { 25 | this.props.actions.getPods(); 26 | this.props.actions.connectToSocket(); 27 | } 28 | 29 | componentWillReceiveProps (newProps) { 30 | if (newProps.pods !== this.props.pods) { 31 | this.setState({ 32 | scaleCount: newProps.pods.length 33 | }); 34 | } 35 | } 36 | 37 | componentWillUnmount () { 38 | this.props.actions.disconnectFromSocket(); 39 | } 40 | 41 | handleConsecutiveRequestSlider (value) { 42 | this.setState({ 43 | consecutiveRequestCount: value 44 | }); 45 | } 46 | 47 | submitConsecutiveRequests () { 48 | this.props.actions.submitConsecutiveRequests(this.state.consecutiveRequestCount); 49 | } 50 | 51 | handleScaleSlider (value) { 52 | this.setState({ 53 | scaleCount: value 54 | }); 55 | } 56 | 57 | onScale () { 58 | this.props.actions.scale(this.state.scaleCount); 59 | } 60 | 61 | render () { 62 | const scaleProps = Object.assign({}, instanceConfig, { 63 | onChange: this.handleScaleSlider, 64 | onScale: this.onScale 65 | }); 66 | const consecutiveRequestProps = { 67 | min: 10, 68 | max: 500, 69 | step: 10, 70 | value: this.state.consecutiveRequestCount, 71 | onChange: this.handleConsecutiveRequestSlider 72 | }; 73 | 74 | return (
75 | 80 | 81 |
82 | 83 | 84 |
85 |
86 |

Choose the number of consecutive requests you want to make and click "Load Test." 87 | This will call the puzzle service the given number of times and you can observe the calls being made in the instance grid above.

88 |
89 |
); 90 | } 91 | } 92 | 93 | InstancesComponent.propTypes = { 94 | actions: PropTypes.objectOf(PropTypes.func), 95 | state: PropTypes.object, 96 | pods: PropTypes.array, 97 | activeInstance: PropTypes.string 98 | }; 99 | 100 | function mapStateToProps (state) { 101 | return { 102 | pods: state.webSocket.pods, 103 | activeInstance: state.webSocket.activePod 104 | }; 105 | } 106 | 107 | function mapDispatchToProps (dispatch) { 108 | return { 109 | actions: bindActionCreators(actions, dispatch) 110 | }; 111 | } 112 | 113 | export default connect(mapStateToProps, mapDispatchToProps)(InstancesComponent); 114 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/actions/puzzleActions.js: -------------------------------------------------------------------------------- 1 | import fetchRetry from 'fetch-retry'; 2 | import * as actions from './actionTypes'; 3 | import constants from '../constants'; 4 | const baseUrl = `http://puzzle.${constants.minikubeIp}.nip.io/puzzle/v1`; 5 | const arrowDisplayTime = 1000; 6 | 7 | export function getPuzzleDataSuccess (json) { 8 | return {type: actions.puzzle.GET_PUZZLE_DATA_SUCCESS, data: json}; 9 | } 10 | 11 | export function getPuzzleDataFailure () { 12 | return {type: actions.puzzle.GET_PUZZLE_DATA_FAILURE}; 13 | } 14 | 15 | export function getPuzzleData () { 16 | return dispatch => { 17 | dispatch({type: actions.puzzle.PUZZLE_LOADING}); 18 | return fetchRetry(`${baseUrl}/crossword`, { 19 | retries: 36, 20 | retryDelay: 5000 21 | }) 22 | .then((resp) => { 23 | return resp.json(); 24 | }) 25 | .then((json) => { 26 | if (json.fromCache) { 27 | dispatch({type: actions.puzzle.FROM_CACHE, data: true}); 28 | setTimeout(() => { 29 | return dispatch({type: actions.puzzle.FROM_CACHE, data: false}); 30 | }, arrowDisplayTime); 31 | } else { 32 | dispatch({type: actions.puzzle.FROM_MONGO, data: true}); 33 | setTimeout(() => { 34 | return dispatch({type: actions.puzzle.FROM_MONGO, data: false}); 35 | }, arrowDisplayTime); 36 | } 37 | 38 | return dispatch(getPuzzleDataSuccess(json)); 39 | }) 40 | .catch(err => { 41 | console.log(err); 42 | dispatch(getPuzzleDataFailure(err)); 43 | }); 44 | }; 45 | } 46 | 47 | export function submitPuzzleDataSuccess (json) { 48 | return {type: actions.puzzle.SUBMIT_PUZZLE_DATA_SUCCESS, data: json}; 49 | } 50 | 51 | export function submitPuzzleDataFailure () { 52 | return {type: actions.puzzle.SUBMIT_PUZZLE_DATA_FAILURE}; 53 | } 54 | 55 | export function submitPuzzleData (data) { 56 | return dispatch => { 57 | const submission = JSON.stringify(data); 58 | const headers = new Headers(); 59 | headers.append('Content-Type', 'application/json'); 60 | 61 | return fetch(`${baseUrl}/crossword`, { 62 | method: 'PUT', 63 | headers, 64 | body: submission 65 | }) 66 | .then((resp) => { 67 | if (resp.status === 204) { 68 | dispatch(submitPuzzleDataSuccess(data)); 69 | } else { 70 | dispatch(submitPuzzleDataFailure); 71 | } 72 | }) 73 | .catch((err) => { 74 | dispatch(submitPuzzleDataFailure); 75 | }); 76 | }; 77 | } 78 | 79 | export function clearPuzzleData (data) { 80 | return dispatch => { 81 | return fetch(`${baseUrl}/crossword/clear`, { 82 | method: 'PUT' 83 | }) 84 | .then((resp) => { 85 | if (resp.status === 204) { 86 | dispatch({type: actions.puzzle.CLEAR_PUZZLE_DATA, data}); 87 | } 88 | }) 89 | .catch((err) => { 90 | console.log(err); 91 | }); 92 | }; 93 | } 94 | 95 | export function sendingData () { 96 | return dispatch => { 97 | dispatch({type: actions.puzzle.SENDING_DATA, data: true}); 98 | setTimeout(() => { 99 | return dispatch({type: actions.puzzle.SENDING_DATA, data: false}); 100 | }, arrowDisplayTime); 101 | }; 102 | } 103 | 104 | export function fromCache () { 105 | return dispatch => { 106 | dispatch({type: actions.puzzle.FROM_CACHE, data: true}); 107 | setTimeout(() => { 108 | return dispatch({type: actions.puzzle.FROM_CACHE, data: false}); 109 | }, arrowDisplayTime); 110 | }; 111 | } 112 | 113 | export function fromMongo () { 114 | return dispatch => { 115 | dispatch({type: actions.puzzle.FROM_MONGO, data: true}); 116 | setTimeout(() => { 117 | return dispatch({type: actions.puzzle.FROM_MONGO, data: false}); 118 | }, arrowDisplayTime); 119 | }; 120 | } 121 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kenzan-kr8ssword-puzzle", 3 | "version": "1.0.0", 4 | "description": "A simple crossword puzzle showing the integration of containerized microservices, in a react, etcd and mongodb stack managed inside a kubernetes cluster", 5 | "main": "index.js", 6 | "scripts": { 7 | "develop": "gulp serve:dev && npm run lint", 8 | "start": "gulp serve:prod", 9 | "build": "npm test && gulp build:dev", 10 | "test": "ava --verbose", 11 | "test:watch": "ava --verbose --watch", 12 | "documentation": "react-docgen ./src/components/**/*.js ./src/components/*.js -o react-doc.json --pretty", 13 | "lint": "eslint ./src/*.js ./src/**/*.js" 14 | }, 15 | "babel": { 16 | "presets": [ 17 | "es2015", 18 | "react" 19 | ] 20 | }, 21 | "maintainers": [ 22 | { 23 | "name": "Scott Pullano", 24 | "email": "spullano@kenzan.com" 25 | } 26 | ], 27 | "contributors": [ 28 | { 29 | "name": "Scott Pullano", 30 | "email": "spullano@kenzan.com" 31 | }, 32 | { 33 | "name": "Justin Tomlinson", 34 | "email": "jtomlinson@kenzan.com" 35 | } 36 | ], 37 | "license": "UNLICENSED", 38 | "dependencies": { 39 | "babel-polyfill": "6.13.0", 40 | "chance": "1.0.4", 41 | "classnames": "^2.2.5", 42 | "d3": "4.4.1", 43 | "fetch-retry": "^1.1.1", 44 | "jquery": "3.1.1", 45 | "js-cookie": "^2.1.3", 46 | "lodash.capitalize": "^4.2.1", 47 | "lodash.unionby": "^4.8.0", 48 | "md5": "^2.2.1", 49 | "query-string": "^4.3.1", 50 | "react": "15.3.2", 51 | "react-dom": "15.3.2", 52 | "react-redux": "5.0.2", 53 | "react-router": "3.0.0", 54 | "react-rte": "^0.11.0", 55 | "react-select": "1.0.0-rc.2", 56 | "react-slider": "0.7.0", 57 | "react-table": "^4.3.0", 58 | "react-tap-event-plugin": "^2.0.1", 59 | "redux": "3.6.0", 60 | "redux-devtools-extension": "1.0.0", 61 | "redux-immutable-state-invariant": "1.2.4", 62 | "redux-thunk": "2.1.0", 63 | "showdown": "^1.6.0", 64 | "socket.io-client": "^1.7.3", 65 | "thunk": "0.0.1", 66 | "toastr": "^2.1.2", 67 | "trae": "^1.1.1" 68 | }, 69 | "devDependencies": { 70 | "async": "^2.1.4", 71 | "autoprefixer": "6.4.1", 72 | "ava": "0.16.0", 73 | "babel-cli": "6.14.0", 74 | "babel-loader": "6.2.5", 75 | "babel-preset-es2015": "6.14.0", 76 | "babel-preset-react": "6.11.1", 77 | "babel-register": "6.16.3", 78 | "browser-env": "2.0.11", 79 | "browser-sync": "2.16.0", 80 | "connect-history-api-fallback": "1.3.0", 81 | "del": "2.2.2", 82 | "enzyme": "2.4.1", 83 | "eslint": "^3.6.0", 84 | "eslint-config-semistandard": "^7.0.0", 85 | "eslint-plugin-import": "1.16.0", 86 | "eslint-plugin-jsx-a11y": "2.2.2", 87 | "eslint-plugin-react": "6.2.2", 88 | "eslint-watch": "2.1.14", 89 | "file-loader": "^0.10.0", 90 | "gulp": "3.9.1", 91 | "gulp-connect": "^5.0.0", 92 | "gulp-imagemin": "3.0.3", 93 | "gulp-plumber": "1.1.0", 94 | "gulp-postcss": "6.2.0", 95 | "gulp-sass": "2.3.2", 96 | "gulp-sourcemaps": "1.6.0", 97 | "install": "0.8.1", 98 | "json-loader": "^0.5.4", 99 | "lodash": "^4.17.4", 100 | "ncp": "^2.0.0", 101 | "npm": "3.10.7", 102 | "react-addons-test-utils": "15.3.2", 103 | "react-docgen": "2.10.0", 104 | "run-sequence": "1.2.2", 105 | "sc5-styleguide": "^1.5.0", 106 | "semistandard": "9.2.1", 107 | "sinon": "1.17.6", 108 | "susy": "2.2.12", 109 | "webpack": "1.13.2", 110 | "webpack-stream": "3.2.0" 111 | }, 112 | "ava": { 113 | "files": [ 114 | "**/*.test.js" 115 | ], 116 | "require": [ 117 | "babel-register", 118 | "./test/helpers/setup-browser-env.js" 119 | ], 120 | "babel": "inherit", 121 | "failFast": false 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /manifests/jenkins.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolume 2 | apiVersion: v1 3 | metadata: 4 | name: jenkins 5 | labels: 6 | type: local 7 | spec: 8 | capacity: 9 | storage: 2Gi 10 | accessModes: 11 | - ReadWriteOnce 12 | hostPath: 13 | path: "/data/jenkins/" 14 | 15 | --- 16 | 17 | kind: PersistentVolumeClaim 18 | apiVersion: v1 19 | metadata: 20 | name: jenkins-claim 21 | spec: 22 | accessModes: 23 | - ReadWriteOnce 24 | resources: 25 | requests: 26 | storage: 2Gi 27 | --- 28 | 29 | apiVersion: v1 30 | kind: ServiceAccount 31 | metadata: 32 | name: jenkins 33 | namespace: default 34 | automountServiceAccountToken: true 35 | 36 | --- 37 | 38 | apiVersion: rbac.authorization.k8s.io/v1 39 | kind: ClusterRoleBinding 40 | metadata: 41 | name: Jenkins-cluster-admin 42 | roleRef: 43 | apiGroup: rbac.authorization.k8s.io 44 | kind: ClusterRole 45 | name: cluster-admin 46 | subjects: 47 | - kind: ServiceAccount 48 | name: jenkins 49 | namespace: default 50 | 51 | --- 52 | 53 | apiVersion: v1 54 | kind: ConfigMap 55 | metadata: 56 | creationTimestamp: null 57 | name: kubectl-jenkins-context 58 | data: 59 | kubectl-config-context.sh: |- 60 | #!/bin/bash -v 61 | 62 | kubectl config set-credentials jenkins --token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) 63 | kubectl config set-cluster minikube --server="https://192.168.99.100:8443" --certificate-authority="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" 64 | kubectl config set-context jenkins-minikube --cluster=minikube --user=jenkins --namespace=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) 65 | kubectl config use-context jenkins-minikube 66 | chmod 755 ~/.kube/config 67 | 68 | --- 69 | 70 | apiVersion: v1 71 | kind: Service 72 | metadata: 73 | name: jenkins 74 | labels: 75 | app: jenkins 76 | spec: 77 | ports: 78 | - port: 80 79 | targetPort: 8080 80 | selector: 81 | app: jenkins 82 | tier: jenkins 83 | type: NodePort 84 | 85 | --- 86 | 87 | apiVersion: extensions/v1beta1 88 | kind: Deployment 89 | metadata: 90 | name: jenkins 91 | labels: 92 | app: jenkins 93 | spec: 94 | strategy: 95 | type: Recreate 96 | template: 97 | metadata: 98 | labels: 99 | app: jenkins 100 | tier: jenkins 101 | spec: 102 | serviceAccountName: jenkins 103 | initContainers: 104 | - image: lachlanevenson/k8s-kubectl:v1.11.2 105 | name: kubectl-config 106 | command: 107 | - "/bin/sh" 108 | args: 109 | - "/kubectl-config-context.sh" 110 | volumeMounts: 111 | - name: kubeconfig 112 | mountPath: "/root/.kube" 113 | - name: kubectl-jenkins-context 114 | mountPath: "/kubectl-config-context.sh" 115 | subPath: "kubectl-config-context.sh" 116 | containers: 117 | - image: 127.0.0.1:30400/jenkins:latest 118 | name: jenkins 119 | securityContext: 120 | privileged: true 121 | volumeMounts: 122 | - name: kubeconfig 123 | mountPath: /var/jenkins_home/.kube 124 | - name: docker 125 | mountPath: /var/run/docker.sock 126 | - name: jenkins-persistent-storage 127 | mountPath: /var/jenkins_home 128 | ports: 129 | - containerPort: 8080 130 | name: jenkins 131 | volumes: 132 | - name: kubectl-jenkins-context 133 | configMap: 134 | name: kubectl-jenkins-context 135 | items: 136 | - key: kubectl-config-context.sh 137 | path: kubectl-config-context.sh 138 | - name: kubeconfig 139 | emptyDir: {} 140 | - name: docker 141 | hostPath: 142 | path: /var/run/docker.sock 143 | - name: jenkins-persistent-storage 144 | persistentVolumeClaim: 145 | claimName: jenkins-claim -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/settings-old.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([2,3],{ 2 | 3 | /***/ 3022: 4 | /***/ (function(module, exports, __webpack_require__) { 5 | 6 | "use strict"; 7 | /* WEBPACK VAR INJECTION */(function(process) { 8 | 9 | var feedbackUrl = '/feedback'; 10 | var gateHost = '/gate'; 11 | var bakeryDetailUrl = gateHost + '/bakery/logs/{{context.region}}/{{context.status.resourceId}}'; 12 | var authEndpoint = gateHost + '/auth/user'; 13 | var authEnabled = false; 14 | var netflixMode = false; 15 | var chaosEnabled = false; 16 | var fiatEnabled = false; 17 | var entityTagsEnabled = false; 18 | var debugEnabled = true; 19 | 20 | window.spinnakerSettings = { 21 | checkForUpdates: false, 22 | debugEnabled: debugEnabled, 23 | defaultProviders: ['aws', 'gce', 'azure', 'cf', 'kubernetes', 'titus', 'openstack'], 24 | feedbackUrl: feedbackUrl, 25 | gateUrl: gateHost, 26 | bakeryDetailUrl: bakeryDetailUrl, 27 | authEndpoint: authEndpoint, 28 | pollSchedule: 30000, 29 | defaultTimeZone: process.env.TIMEZONE || 'America/Los_Angeles', // see http://momentjs.com/timezone/docs/#/data-utilities/ 30 | defaultCategory: 'serverGroup', 31 | defaultInstancePort: 80, 32 | providers: { 33 | azure: { 34 | defaults: { 35 | account: 'azure-test', 36 | region: 'westus' 37 | } 38 | }, 39 | aws: { 40 | defaults: { 41 | account: 'test', 42 | region: 'us-east-1', 43 | iamRole: 'BaseIAMRole' 44 | }, 45 | defaultSecurityGroups: [], 46 | loadBalancers: { 47 | // if true, VPC load balancers will be created as internal load balancers if the selected subnet has a purpose 48 | // tag that starts with "internal" 49 | inferInternalFlagFromSubnet: false 50 | }, 51 | useAmiBlockDeviceMappings: false 52 | }, 53 | gce: { 54 | defaults: { 55 | account: 'my-google-account', 56 | region: 'us-central1', 57 | zone: 'us-central1-f' 58 | } 59 | }, 60 | titus: { 61 | defaults: { 62 | account: 'titustestvpc', 63 | region: 'us-east-1', 64 | iamProfile: '{{application}}InstanceProfile' 65 | } 66 | }, 67 | openstack: { 68 | defaults: { 69 | account: 'test', 70 | region: 'us-west-1' 71 | } 72 | }, 73 | kubernetes: { 74 | defaults: { 75 | account: 'my-kubernetes-account', 76 | namespace: 'default', 77 | proxy: 'localhost:8080' 78 | } 79 | }, 80 | appengine: { 81 | defaults: { 82 | account: 'my-appengine-account', 83 | editLoadBalancerStageEnabled: false 84 | } 85 | } 86 | }, 87 | whatsNew: { 88 | gistId: '32526cd608db3d811b38', 89 | fileName: 'news.md' 90 | }, 91 | notifications: { 92 | email: { 93 | enabled: true 94 | }, 95 | hipchat: { 96 | enabled: true, 97 | botName: 'Skynet T-800' 98 | }, 99 | sms: { 100 | enabled: true 101 | }, 102 | slack: { 103 | enabled: true, 104 | botName: 'spinnakerbot' 105 | } 106 | }, 107 | authEnabled: authEnabled, 108 | authTtl: 600000, 109 | gitSources: ['stash', 'github', 'bitbucket'], 110 | triggerTypes: ['git', 'pipeline', 'docker', 'cron', 'jenkins'], 111 | feature: { 112 | entityTags: entityTagsEnabled, 113 | fiatEnabled: fiatEnabled, 114 | pipelines: true, 115 | notifications: false, 116 | fastProperty: true, 117 | vpcMigrator: true, 118 | clusterDiff: false, 119 | roscoMode: false, 120 | netflixMode: netflixMode, 121 | chaosMonkey: chaosEnabled, 122 | // whether stages affecting infrastructure (like "Create Load Balancer") should be enabled or not 123 | infrastructureStages: process.env.INFRA_STAGES === 'enabled', 124 | jobs: false, 125 | snapshots: false 126 | } 127 | }; 128 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(180))) 129 | 130 | /***/ }) 131 | 132 | },[3022]); -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/settings.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([2,3],{ 2 | 3 | /***/ 3304: 4 | /***/ (function(module, exports, __webpack_require__) { 5 | 6 | "use strict"; 7 | /* WEBPACK VAR INJECTION */(function(process) { 8 | 9 | var feedbackUrl = process.env.FEEDBACK_URL; 10 | var gateHost = process.env.API_HOST || '/gate'; 11 | var bakeryDetailUrl = process.env.BAKERY_DETAIL_URL || gateHost + '/bakery/logs/{{context.region}}/{{context.status.resourceId}}'; 12 | var authEndpoint = process.env.AUTH_ENDPOINT || gateHost + '/auth/user'; 13 | var authEnabled = false; 14 | var netflixMode = false; 15 | var chaosEnabled = netflixMode || process.env.CHAOS_ENABLED === 'true' ? true : false; 16 | var fiatEnabled = false; 17 | var entityTagsEnabled = false; 18 | var debugEnabled = process.env.DEBUG_ENABLED === 'false' ? false : true; 19 | 20 | window.spinnakerSettings = { 21 | checkForUpdates: false, 22 | debugEnabled: debugEnabled, 23 | defaultProviders: ['aws', 'gce', 'azure', 'cf', 'kubernetes', 'titus', 'openstack'], 24 | feedbackUrl: feedbackUrl, 25 | gateUrl: gateHost, 26 | bakeryDetailUrl: bakeryDetailUrl, 27 | authEndpoint: authEndpoint, 28 | pollSchedule: 30000, 29 | defaultTimeZone: process.env.TIMEZONE || 'America/Los_Angeles', // see http://momentjs.com/timezone/docs/#/data-utilities/ 30 | defaultCategory: 'serverGroup', 31 | defaultInstancePort: 80, 32 | providers: { 33 | azure: { 34 | defaults: { 35 | account: 'azure-test', 36 | region: 'westus' 37 | } 38 | }, 39 | aws: { 40 | defaults: { 41 | account: 'test', 42 | region: 'us-east-1', 43 | iamRole: 'BaseIAMRole' 44 | }, 45 | defaultSecurityGroups: [], 46 | loadBalancers: { 47 | // if true, VPC load balancers will be created as internal load balancers if the selected subnet has a purpose 48 | // tag that starts with "internal" 49 | inferInternalFlagFromSubnet: false 50 | }, 51 | useAmiBlockDeviceMappings: false 52 | }, 53 | gce: { 54 | defaults: { 55 | account: 'my-google-account', 56 | region: 'us-central1', 57 | zone: 'us-central1-f' 58 | } 59 | }, 60 | titus: { 61 | defaults: { 62 | account: 'titustestvpc', 63 | region: 'us-east-1', 64 | iamProfile: '{{application}}InstanceProfile' 65 | } 66 | }, 67 | openstack: { 68 | defaults: { 69 | account: 'test', 70 | region: 'us-west-1' 71 | } 72 | }, 73 | kubernetes: { 74 | defaults: { 75 | account: 'default', 76 | namespace: 'default', 77 | proxy: 'localhost:8001' 78 | } 79 | }, 80 | appengine: { 81 | defaults: { 82 | account: 'my-appengine-account', 83 | editLoadBalancerStageEnabled: false 84 | } 85 | } 86 | }, 87 | whatsNew: { 88 | gistId: '32526cd608db3d811b38', 89 | fileName: 'news.md' 90 | }, 91 | notifications: { 92 | email: { 93 | enabled: true 94 | }, 95 | hipchat: { 96 | enabled: true, 97 | botName: 'Skynet T-800' 98 | }, 99 | sms: { 100 | enabled: true 101 | }, 102 | slack: { 103 | enabled: true, 104 | botName: 'spinnakerbot' 105 | } 106 | }, 107 | authEnabled: authEnabled, 108 | authTtl: 600000, 109 | gitSources: ['stash', 'github', 'bitbucket'], 110 | triggerTypes: ['git', 'pipeline', 'docker', 'cron', 'jenkins'], 111 | feature: { 112 | entityTags: entityTagsEnabled, 113 | fiatEnabled: fiatEnabled, 114 | pipelines: true, 115 | notifications: false, 116 | fastProperty: true, 117 | vpcMigrator: true, 118 | clusterDiff: false, 119 | roscoMode: false, 120 | netflixMode: netflixMode, 121 | chaosMonkey: chaosEnabled, 122 | // whether stages affecting infrastructure (like "Create Load Balancer") should be enabled or not 123 | infrastructureStages: process.env.INFRA_STAGES === 'enabled', 124 | jobs: false, 125 | snapshots: false 126 | } 127 | }; 128 | /* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(224))) 129 | 130 | /***/ }) 131 | 132 | },[3304]); -------------------------------------------------------------------------------- /part1.yml: -------------------------------------------------------------------------------- 1 | 2 | parts: 3 | 4 | - name: Part 1 5 | intro: In this part we will setup a local cluster with minikube, deploy a public image from dockerhub, customize that image, and then finally deploy it inside our local cluster. 6 | steps: 7 | 8 | - cap: Start up the Kubernetes cluster with Minikube, giving it some extra resources. 9 | com: minikube start --memory 8000 --cpus 2 --vm-driver=virtualbox --kubernetes-version v1.11.10 10 | 11 | - cap: Enable the Minikube add-ons Heapster and Ingress. 12 | com: minikube addons enable heapster; minikube addons enable ingress 13 | 14 | - cap: View the Minikube Dashboard, a web UI for managing deployments. 15 | com: minikube service kubernetes-dashboard --namespace kube-system 16 | 17 | - cap: Deploy the public nginx image from DockerHub into a pod. Nginx is an open source web server that will automatically download from Docker Hub if it’s not available locally. 18 | com: kubectl run nginx --image nginx --port 80 19 | 20 | - cap: Create a K8s Service for the deployment. This will expose the nginx pod so you can access it with a web browser. 21 | com: kubectl expose deployment nginx --type NodePort --port 80 22 | 23 | - cap: Launch a web browser to test the service. The nginx welcome page displays, which means the service is up and running. 24 | com: minikube service nginx 25 | 26 | - cap: Delete the nginx deployment and service you created. 27 | com: kubectl delete service nginx; kubectl delete deployment nginx 28 | 29 | - cap: Set up the cluster registry by applying a .yaml manifest file. 30 | com: kubectl apply -f manifests/registry.yaml 31 | 32 | - cap: Wait for the registry to finish deploying. Note that this may take several minutes. 33 | com: kubectl rollout status deployments/registry 34 | 35 | - cap: View the registry user interface in a web browser. 36 | com: minikube service registry-ui 37 | 38 | - cap: Let’s make a change to an HTML file in the cloned project. Open the /applications/hello-kenzan/index.html file in your favorite text editor (for example, you can use nano by running the command 'nano applications/hello-kenzan/index.html' in a separate terminal). Change some text inside one of the

tags. For example, change “Hello from Kenzan!” to “Hello from Me!”. Save the file. 39 | com: echo '' 40 | 41 | - cap: Now let’s build an image, giving it a special name that points to our local cluster registry. 42 | com: docker build -t 127.0.0.1:30400/hello-kenzan:latest -f applications/hello-kenzan/Dockerfile applications/hello-kenzan 43 | 44 | - cap: We’ve built the image, but before we can push it to the registry, we need to set up a temporary proxy. By default the Docker client can only push to HTTP (not HTTPS) via localhost. To work around this, we’ll set up a Docker container that listens on 127.0.0.1:30400 and forwards to our cluster. First, build the image for our proxy container. 45 | com: docker build -t socat-registry -f applications/socat/Dockerfile applications/socat 46 | 47 | - cap: Now run the proxy container from the newly created image. (Note that you may see some errors; this is normal as the commands are first making sure there are no previous instances running.) 48 | com: docker stop socat-registry; docker rm socat-registry; docker run -d -e "REG_IP=`minikube ip`" -e "REG_PORT=30400" --name socat-registry -p 30400:5000 socat-registry 49 | 50 | - cap: With our proxy container up and running, we can now push our hello-kenzan image to the local repository. 51 | com: docker push 127.0.0.1:30400/hello-kenzan:latest 52 | 53 | - cap: The proxy’s work is done, so you can go ahead and stop it. 54 | com: docker stop socat-registry; 55 | 56 | - cap: With the image in our cluster registry, the last thing to do is apply the manifest to create and deploy the hello-kenzan pod based on the image. 57 | com: kubectl apply -f applications/hello-kenzan/k8s/manual-deployment.yaml 58 | 59 | - cap: Launch a web browser and view the service. 60 | com: minikube service hello-kenzan 61 | 62 | - cap: Delete the hello-kenzan deployment and service you created. 63 | com: kubectl delete service hello-kenzan; kubectl delete deployment hello-kenzan 64 | -------------------------------------------------------------------------------- /part2.yml: -------------------------------------------------------------------------------- 1 | parts: 2 | 3 | - name: Part 2 4 | intro: In this part we will Setup Jenkins, and setup an automated pipeline to build, push and deploy our custom appliction. 5 | steps: 6 | 7 | - cap: Start up the Kubernetes cluster with Minikube. 8 | com: minikube stop; minikube start --memory 8000 --cpus 2 --kubernetes-version v1.11.0 9 | 10 | - cap: Before we do anything, let's make sure the private registry deployment we've setup in the previous part is available. 11 | com: kubectl rollout status deployment/registry 12 | 13 | - cap: Let's build the Jenkins Docker image we'll use in our Kubernetes cluster. 14 | com: docker build -t 127.0.0.1:30400/jenkins:latest -f applications/jenkins/Dockerfile applications/jenkins 15 | 16 | - cap: Once again we'll need to set up the Socat Registry proxy container to push images, so let's build it. Feel free to skip this step in case the socat-registry image already exists from Part 1 (to check, run `docker images` in a separate terminal). 17 | com: docker build -t socat-registry -f applications/socat/Dockerfile applications/socat 18 | 19 | - cap: Run the proxy container from the image. 20 | com: docker stop socat-registry; docker rm socat-registry; docker run -d -e "REG_IP=`minikube ip`" -e "REG_PORT=30400" --name socat-registry -p 30400:5000 socat-registry; sleep 10 21 | 22 | - cap: With our proxy container up and running, we can now push our Jenkins image to the local repository. 23 | com: docker push 127.0.0.1:30400/jenkins:latest 24 | 25 | - cap: The proxy’s work is done, so you can go ahead and stop it. 26 | com: docker stop socat-registry 27 | 28 | - cap: Deploy Jenkins, which we’ll use to create our automated CI/CD pipeline. It will take the pod a minute or two to roll out. 29 | com: kubectl apply -f manifests/jenkins.yaml; kubectl rollout status deployment/jenkins 30 | 31 | - cap: Open the Jenkins UI in a web browser. 32 | com: minikube service jenkins 33 | 34 | - cap: Display the Jenkins admin password with the following command, and right-click to copy it. IMPORTANT: BE CAREFUL NOT TO PRESS CTRL-C TO COPY THE PASSWORD AS THIS WILL STOP THE SCRIPT 35 | com: kubectl exec -it `kubectl get pods --selector=app=jenkins --output=jsonpath={.items..metadata.name}` cat /var/jenkins_home/secrets/initialAdminPassword 36 | 37 | - cap: Switch back to the Jenkins UI. Paste the Jenkins admin password in the box and click Continue. Click Install suggested plugins. Plugins have actually been pre-downloaded during the Jenkins image build, so this step should finish fairly quickly. 38 | com: echo '' 39 | 40 | - cap: Create an admin user and credentials, and click Save and Continue. (Make sure to remember these credentials as you will need them for repeated logins.) On the Instance Configuration page, click Save and Finish. On the next page, click Restart (if it appears to hang for some time on restarting, you may have to refresh the browser window). Login to Jenkins. 41 | com: echo '' 42 | 43 | - cap: Before we create a pipeline, we first need to provision the Kubernetes Continuous Deploy plugin with a kubeconfig file that will allow access to our Kubernetes cluster. In Jenkins on the left, click on Credentials, select the Jenkins store, then Global credentials (unrestricted), and Add Credentials on the left menu. 44 | com: echo '' 45 | 46 | - cap: The following values must be entered precisely as indicated: for the Kind field select the option `Kubernetes configuration (kubeconfig)`, set the ID as `kenzan_kubeconfig`, set Kubeconfig to `From a file on the Jenkins master`, and specify the the file path as `/var/jenkins_home/.kube/config`. Click the OK button. 47 | com: echo '' 48 | 49 | - cap: We now want to create a new pipeline for use with our Hello-Kenzan app. Back on Jenkins home, on the left, click New Item. Enter the item name as "Hello-Kenzan Pipeline", select Pipeline, and click OK. 50 | com: echo '' 51 | 52 | - cap: Under the Pipeline section at the bottom, change the Definition to be "Pipeline script from SCM". 53 | com: echo '' 54 | 55 | - cap: Change the SCM to Git. Change the Repository URL to be the URL of your forked Git repository, such as https://github.com/[GIT USERNAME]/kubernetes-ci-cd. Click Save. On the left, click Build Now to run the new pipeline. 56 | com: echo '' 57 | 58 | - cap: After all pipeline stages are colored green as complete, view the Hello-Kenzan application. 59 | com: minikube service hello-kenzan 60 | 61 | - cap: Push a change to your fork. Run the job again. View the changes. 62 | com: minikube service hello-kenzan 63 | -------------------------------------------------------------------------------- /part3.yml: -------------------------------------------------------------------------------- 1 | parts: 2 | 3 | - name: Part 3 4 | intro: This part will have us setup the various applications that will present the crossword puzzle. We will run a sample etcd cluster as a cache, a pages application containing the front-end, a crossword server using mongodb, and a monitoring and scaling server application. 5 | steps: 6 | 7 | - cap: Initialize Helm. This will install Tiller (Helm's server) into our Kubernetes cluster. 8 | com: helm init --wait --debug; kubectl rollout status deploy/tiller-deploy -n kube-system 9 | 10 | - cap: We will deploy the etcd operator onto the cluster using a Helm Chart. 11 | com: helm install stable/etcd-operator --version 0.8.0 --name etcd-operator --debug --wait 12 | 13 | - cap: Deploy the etcd cluster and K8s Services for accessing the cluster. 14 | com: kubectl create -f manifests/etcd-cluster.yaml; kubectl create -f manifests/etcd-service.yaml 15 | 16 | - cap: The crossword application is a multi-tier application whose services depend on each other. We will create three K8s Services so that the applications can communicate with one another. 17 | com: kubectl apply -f manifests/all-services.yaml 18 | 19 | - cap: Now we're going to walk through an initial build of the monitor-scale service. 20 | com: docker build -t 127.0.0.1:30400/monitor-scale:`git rev-parse --short HEAD` -f applications/monitor-scale/Dockerfile applications/monitor-scale 21 | 22 | - cap: Once again we'll need to set up the Socat Registry proxy container to push the monitor-scale image to our registry, so let's build it. Feel free to skip this step in case the socat-registry image already exists from Part 2 (to check, run `docker images` in a separate terminal). 23 | com: docker build -t socat-registry -f applications/socat/Dockerfile applications/socat 24 | 25 | - cap: Run the proxy container from the newly created image. 26 | com: docker stop socat-registry; docker rm socat-registry; docker run -d -e "REG_IP=`minikube ip`" -e "REG_PORT=30400" --name socat-registry -p 30400:5000 socat-registry; sleep 10 27 | 28 | - cap: Push the monitor-scale image to the registry. 29 | com: docker push 127.0.0.1:30400/monitor-scale:`git rev-parse --short HEAD` 30 | 31 | - cap: The proxy’s work is done, so go ahead and stop it. 32 | com: docker stop socat-registry 33 | 34 | - cap: Open the registry UI and verify that the monitor-scale image is in our local registry. 35 | com: minikube service registry-ui 36 | 37 | - cap: Monitor-scale has the functionality to let us scale our puzzle app up and down through the Kr8sswordz UI, therefore we'll need to do some RBAC work in order to provide monitor-scale with the proper rights. 38 | com: kubectl apply -f manifests/monitor-scale-serviceaccount.yaml 39 | 40 | - cap: Create the monitor-scale deployment and the Ingress defining the hostname by which this service will be accessible to the other services. 41 | com: sed 's#127.0.0.1:30400/monitor-scale:$BUILD_TAG#127.0.0.1:30400/monitor-scale:'`git rev-parse --short HEAD`'#' applications/monitor-scale/k8s/deployment.yaml | kubectl apply -f - 42 | 43 | - cap: Wait for the monitor-scale deployment to finish. 44 | com: kubectl rollout status deployment/monitor-scale 45 | 46 | - cap: View pods to see the monitor-scale pod running. 47 | com: kubectl get pods 48 | 49 | - cap: View services to see the monitor-scale service. 50 | com: kubectl get services 51 | 52 | - cap: View ingress rules to see the monitor-scale ingress rule. 53 | com: kubectl get ingress 54 | 55 | - cap: View deployments to see the monitor-scale deployment. 56 | com: kubectl get deployments 57 | 58 | - cap: We will run a script to bootstrap the puzzle and mongo services, creating Docker images and storing them in the local registry. The puzzle.sh script runs through the same build, proxy, push, and deploy steps we just ran through manually for both services. 59 | com: scripts/puzzle.sh 60 | 61 | - cap: Check to see if the puzzle and mongo services have been deployed. 62 | com: kubectl rollout status deployment/puzzle; kubectl rollout status deployment/mongo 63 | 64 | - cap: Bootstrap the kr8sswordz frontend web application. This script follows the same build proxy, push, and deploy steps that the other services followed. 65 | com: scripts/kr8sswordz-pages.sh 66 | 67 | - cap: Check to see if the frontend has been deployed. 68 | com: kubectl rollout status deployment/kr8sswordz 69 | 70 | - cap: Check out all the pods that are running. 71 | com: kubectl get pods 72 | 73 | - cap: Start the web application in your default browser. You may have to refresh your browser so that the puzzle appears properly. 74 | com: minikube service kr8sswordz -------------------------------------------------------------------------------- /applications/monitor-scale/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | var http = require('http').Server(app); 4 | var request = require('request'); 5 | var async = require('async'); 6 | var io = require('socket.io')(http); 7 | var path = require("path"); 8 | var Etcd = require('node-etcd'); 9 | var cors = require('cors'); 10 | 11 | app.use(express.static('public')); 12 | 13 | var bodyParser = require("body-parser"); 14 | 15 | app.use(bodyParser.urlencoded({ extended: false })); 16 | app.use(bodyParser.json()); 17 | app.use(cors()); 18 | 19 | etcd = new Etcd("http://example-etcd-cluster-client-service:2379"); 20 | etcd.mkdirSync('pod-list'); 21 | 22 | var watcher = etcd.watcher("pod-list", null, {recursive: true}); 23 | watcher.on("change", function(val) { 24 | 25 | var podChange = { pods: val.node.key, action: val.action }; 26 | console.log(JSON.stringify(podChange)); 27 | io.emit('pods', podChange); 28 | }); 29 | 30 | app.post('/scale', function (req, res) { 31 | var scale = req.body.count; 32 | console.log('Count requested is: %s', scale); 33 | var url = "http://127.0.0.1:2345/apis/extensions/v1beta1/namespaces/default/deployments/puzzle/scale"; 34 | var putBody = { 35 | kind:"Scale", 36 | apiVersion:"extensions/v1beta1", 37 | metadata: { 38 | name:"puzzle", 39 | namespace:"default" 40 | }, 41 | spec: { 42 | replicas:1 43 | }, 44 | status:{} 45 | }; 46 | putBody.spec.replicas = scale; 47 | 48 | request({ url: url, method: 'PUT', json: putBody}, function (err, httpResponse, body) { 49 | if (err) { 50 | console.error('Failed to scale:', err); 51 | next(err); 52 | } 53 | console.log('Response: ' + JSON.stringify(httpResponse)); 54 | res.status(httpResponse.statusCode).json(body); 55 | }); 56 | }); 57 | 58 | app.post('/loadtest/concurrent', function (req, res) { 59 | 60 | var count = req.body.count; 61 | console.log('Count requested is: %s', count); 62 | var url = "http://puzzle:3000/puzzle/v1/crossword"; 63 | var myUrls = []; 64 | for (var i = 0; i < req.body.count; i++) { 65 | myUrls.push(url); 66 | } 67 | async.map(myUrls, function(url, callback) { 68 | request(url, function(error, response, html){ 69 | if (response && response.hasOwnProperty("statusCode")) { 70 | console.log(response.statusCode); 71 | } else { 72 | console.log("Error: " + error); 73 | } 74 | }); 75 | }, function(err, results) { 76 | console.log(results); 77 | }); 78 | res.send('concurrent done'); 79 | }); 80 | 81 | app.post('/loadtest/consecutive', function (req, res) { 82 | 83 | var count = req.body.count; 84 | var url = "http://puzzle:3000/puzzle/v1/crossword"; 85 | var callArray = []; 86 | 87 | for (var i = 0; i < req.body.count; i++) { 88 | 89 | callArray.push(function (cb) { 90 | setTimeout(function () { 91 | request(url, function(error, response, html) { 92 | cb(null, response && response.statusCode); 93 | }); 94 | }, 100); 95 | }); 96 | } 97 | async.series(callArray, function (err, results) { 98 | var finalCount = results && results.length; 99 | console.log(`${finalCount} requests sent.`) 100 | }); 101 | res.send('consecutive done'); 102 | }); 103 | 104 | app.get('/up/:podId', function (req, res) { 105 | console.log('Server UP: %s', req.params.podId); 106 | etcd.setSync("pod-list/" + req.params.podId, req.params.podId); 107 | res.send('up done'); 108 | }); 109 | 110 | app.get('/down/:podId', function (req, res) { 111 | console.log('Server DOWN: %s', req.params.podId); 112 | etcd.delSync("pod-list/" + req.params.podId, req.params.podId); 113 | res.send('down done'); 114 | }); 115 | 116 | app.get('/hit/:podId', function (req, res) { 117 | 118 | var d = new Date(); 119 | var n = d.getTime(); 120 | 121 | console.log("Emitting hit from %s", req.params.podId); 122 | io.emit('hit', { podId: req.params.podId, time: n }); 123 | res.send('hit done') 124 | }); 125 | 126 | app.get('/pods', function (req, res) { 127 | var pods = etcd.getSync("pod-list",{ recursive: true }); 128 | res.setHeader('Content-Type', 'application/json'); 129 | res.send(JSON.stringify({pods: pods.body.node.nodes})); 130 | }); 131 | 132 | app.delete('/pods', function (req, res) { 133 | 134 | var pods = etcd.delSync("pod-list/",{ recursive: true }); 135 | res.send('pods deleted') 136 | }); 137 | 138 | io.on('connection', function(socket){ 139 | 140 | console.log("Websocket connection established."); 141 | socket.on('disconnect', function() { 142 | console.log("Websocket disconnect."); 143 | }) 144 | }); 145 | 146 | app.get('/', function(req,res){ 147 | res.send('basic GET successful'); 148 | }); 149 | 150 | http.listen(3001, function () { 151 | console.log('Listening on port 3001!'); 152 | }); 153 | 154 | -------------------------------------------------------------------------------- /applications/spinnaker-helm/spinnaker-chart/static/gate.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8084 3 | address: 0.0.0.0 4 | 5 | spinnaker: 6 | redis: 7 | enabled: true 8 | connection: redis://redis:6379 9 | scheduler: default 10 | parallelism: -1 11 | 12 | services: 13 | deck: 14 | baseUrl: http://spinnaker.192.168.42.74.xip.io 15 | auth: 16 | enabled: false 17 | orca: 18 | baseUrl: http://orca:8083 19 | front50: 20 | baseUrl: http://front50:8080 21 | clouddriver: 22 | baseUrl: http://clouddriver:7002 23 | # semi-optional services (Everybody except Netflix needs these): 24 | rosco: 25 | enabled: false 26 | baseUrl: http://rosco:8087 27 | # These are here only for Netflix, whose bakery does not support dynamically fetching these properties. 28 | # Modifying the details below when rosco.enabled is true will have no effect (do it in the Rosco config). 29 | # defaults: 30 | # bakeOptions: 31 | # - cloudProvider: aws 32 | # baseImages: 33 | # - id: trusty 34 | # shortDescription: v14.04 35 | # detailedDescription: Ubuntu Trusty Tahr v14.04 36 | # packageType: DEB 37 | # - id: ubuntu 38 | # shortDescription: v12.04 39 | # detailedDescription: Ubuntu Precise Pangolin v12.04 40 | # packageType: DEB 41 | # - id: centos 42 | # shortDescription: deprecated 43 | # detailedDescription: CentOS v5.11 44 | # packageType: RPM 45 | # optional services: 46 | echo: 47 | enabled: false 48 | fiat: 49 | enabled: false 50 | autoConfig: false 51 | flapjack: 52 | enabled: false 53 | igor: 54 | enabled: true 55 | baseUrl: http://igor:8088 56 | mahe: 57 | enabled: false 58 | 59 | redis: 60 | enabled: true 61 | connection: redis://redis:6379 62 | scheduler: default 63 | parallelism: -1 64 | 65 | swagger: 66 | enabled: true 67 | title: Spinnaker API 68 | description: 69 | contact: 70 | patterns: 71 | - .*tasks.* 72 | - .*applications.* 73 | - .*securityGroups.* 74 | - /search 75 | - .*pipelines.* 76 | - .*loadBalancers.* 77 | - .*instances.* 78 | - .*images.* 79 | - .*elasticIps.* 80 | - .*credentials.* 81 | - .*events.* 82 | - .*builds.* 83 | - .*instanceTypes.* 84 | - .*vpcs.* 85 | - .*subnets.* 86 | - .*networks.* 87 | - .*bakery.* 88 | - .*executions.* 89 | 90 | hystrix: 91 | command: 92 | ## Hystrix Global Defaults 93 | default: 94 | execution.isolation.thread.timeoutInMilliseconds: 60000 95 | ## Command-specific overrides 96 | fetchGlobalAccounts: 97 | execution.isolation.thread.timeoutInMilliseconds: 2000 98 | 99 | spring: 100 | jackson: 101 | mapper: 102 | SORT_PROPERTIES_ALPHABETICALLY: true 103 | serialization: 104 | ORDER_MAP_ENTRIES_BY_KEYS: true 105 | 106 | --- 107 | 108 | # spring: 109 | # profiles: googleOAuth 110 | # oauth2: 111 | # client: 112 | # # Set these values in your own config file (e.g. spinnaker-local.yml or gate-googleOAuth.yml). 113 | # # clientId: 114 | # # clientSecret: 115 | # accessTokenUri: https://www.googleapis.com/oauth2/v4/token 116 | # userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth 117 | # scope: "profile email" 118 | # resource: 119 | # userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo 120 | # userInfoMapping: 121 | # email: email 122 | # firstName: given_name 123 | # lastName: family_name 124 | 125 | # --- 126 | 127 | # spring: 128 | # profiles: azureOAuth 129 | # oauth2: 130 | # client: 131 | # # Set these values in your own config file (e.g. spinnaker-local.yml or gate-azureOAuth.yml). 132 | # # clientId: 133 | # # clientSecret: 134 | # accessTokenUri: https://login.microsoftonline.com/${azureTenantId}/oauth2/token 135 | # userAuthorizationUri: https://login.microsoftonline.com/${azureTenantId}/oauth2/authorize?resource=https://graph.windows.net 136 | # scope: "profile" 137 | # clientAuthenticationScheme: query 138 | # resource: 139 | # userInfoUri: https://graph.windows.net/me?api-version=1.6 140 | # userInfoMapping: 141 | # email: userPrincipalName 142 | # firstName: givenName 143 | # lastName: surname 144 | 145 | # --- 146 | 147 | # spring: 148 | # profiles: githubOAuth 149 | # oauth2: 150 | # client: 151 | # # Set these values in your own config file (e.g. spinnaker-local.yml or gate-githubOAuth.yml). 152 | # # clientId: 153 | # # clientSecret: 154 | # userAuthorizationUri: https://github.com/login/oauth/authorize 155 | # accessTokenUri: https://github.com/login/oauth/access_token 156 | # scope: user:email 157 | # resource: 158 | # userInfoUri: https://api.github.com/user 159 | # userInfoMapping: 160 | # email: email 161 | # firstName: 162 | # lastName: name 163 | # username: login 164 | 165 | 166 | 167 | 168 | 169 | 170 | # redis: 171 | # enabled: true 172 | # connection: redis://redis:6379 173 | # scheduler: default 174 | # parallelism: -1 175 | # server: 176 | # port: 8084 177 | # address: 0.0.0.0 178 | # services: 179 | # clouddriver: 180 | # baseUrl: http://clouddriver:7002 181 | # deck: 182 | # baseUrl: http://deck.192.168.42.74.xip.io 183 | # front50: 184 | # baseUrl: http://front50:8080 185 | # orca: 186 | # baseUrl: http://orca:8083 187 | # igor: 188 | # enabled: false 189 | # baseUrl: http://igor:8088 190 | # rosco: 191 | # enabled: false 192 | # baseUrl: http://rosco:8087 193 | # echo: 194 | # enabled: false 195 | # baseUrl: http://echo:8089 196 | 197 | # fiat: 198 | # enabled: false 199 | # autoConfig: false 200 | # flapjack: 201 | # enabled: false 202 | # mahe: 203 | # enabled: false -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/components/home/PuzzleComponent.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | import * as puzzleActions from '../../actions/puzzleActions'; 5 | import Cell from '../shared/Cell'; 6 | import _ from 'lodash'; 7 | 8 | class PuzzleComponent extends React.Component { 9 | constructor (props) { 10 | super(props); 11 | 12 | this.state = { 13 | cells: [], 14 | downHints: [], 15 | acrossHints: [], 16 | puzzleGrid: [], 17 | sliderCount: 1 18 | }; 19 | 20 | this.onCellInput = this.onCellInput.bind(this); 21 | this.initializePuzzleArray = this.initializePuzzleArray.bind(this); 22 | this.convertPuzzleGridToPuzzleArray = this.convertPuzzleGridToPuzzleArray.bind(this); 23 | this.reloadPuzzle = this.reloadPuzzle.bind(this); 24 | this.clearPuzzle = this.clearPuzzle.bind(this); 25 | this.submitPuzzle = this.submitPuzzle.bind(this); 26 | } 27 | 28 | componentWillMount () { 29 | this.props.puzzleActions.getPuzzleData(); 30 | } 31 | 32 | componentWillReceiveProps (newProps) { 33 | if (this.props.puzzleData !== newProps.puzzleData) { 34 | this.initializePuzzleArray(newProps.puzzleData); 35 | } 36 | } 37 | 38 | initializeGrid () { 39 | const puzzleGrid = []; 40 | const maxColumns = 12; 41 | const maxRows = 13; 42 | 43 | for (var i = 0; i < maxRows; i++) { 44 | puzzleGrid.push(new Array(maxColumns).fill('')); 45 | } 46 | 47 | return puzzleGrid; 48 | } 49 | 50 | initializePuzzleArray (puzzleArray) { 51 | const downHintsArray = puzzleArray.filter((word) => { 52 | return (word.wordOrientation === 'down'); 53 | }); 54 | const acrossHintsArray = puzzleArray.filter((word) => { 55 | return (word.wordOrientation === 'across'); 56 | }); 57 | 58 | const downHints = this.buildHints(downHintsArray); 59 | const acrossHints = this.buildHints(acrossHintsArray); 60 | 61 | let puzzleGrid = [...this.initializeGrid()]; 62 | puzzleArray.forEach((wordObj, index) => { 63 | const lettersArray = wordObj.word.split(''); 64 | const enteredValueArray = wordObj.enteredValue.split(''); 65 | lettersArray.forEach((letter, index) => { 66 | puzzleGrid = this.addLetterToPuzzleArray(puzzleGrid, wordObj, letter, enteredValueArray[index], index); 67 | }); 68 | }); 69 | 70 | const cells = this.buildCells(puzzleGrid); 71 | 72 | this.setState({ 73 | downHints, 74 | acrossHints, 75 | puzzleGrid, 76 | cells 77 | }); 78 | } 79 | 80 | addLetterToPuzzleArray (puzzleGrid, wordObj, letter, enteredValue, index) { 81 | const letterObj = { 82 | word: wordObj.word, 83 | wordNbr: wordObj.wordNbr, 84 | positionInWord: index, 85 | cellLetter: letter, 86 | currentValue: enteredValue, 87 | wordOrientation: wordObj.wordOrientation, 88 | x: wordObj.wordOrientation === 'across' ? wordObj.startx + index : wordObj.startx, 89 | y: wordObj.wordOrientation === 'across' ? wordObj.starty : wordObj.starty + index 90 | }; 91 | 92 | puzzleGrid[letterObj.y][letterObj.x] = letterObj; 93 | 94 | return puzzleGrid; 95 | } 96 | 97 | buildCells (puzzleGrid) { 98 | return puzzleGrid.map((column, index) => { 99 | return column.map((cell, i) => { 100 | return ( 101 | 112 | ); 113 | }); 114 | }); 115 | } 116 | 117 | onCellInput (e) { 118 | const cellData = e.target.name.split('-'); 119 | const row = cellData[1]; 120 | const col = cellData[2]; 121 | 122 | const puzzleGrid = this.state.puzzleGrid.map((colData, rowIndex) => { 123 | return colData.map((cell, colIndex) => { 124 | if (rowIndex == row && colIndex == col) { 125 | return Object.assign({}, cell, { 126 | currentValue: e.target.value 127 | }); 128 | } else { 129 | return cell; 130 | } 131 | }); 132 | }); 133 | 134 | const cells = this.buildCells(puzzleGrid); 135 | 136 | this.setState({ 137 | puzzleGrid, 138 | cells 139 | }); 140 | } 141 | 142 | buildHints (hintsArray) { 143 | return _.sortBy(hintsArray, 'wordNbr').map((word, index) => { 144 | return ( 145 |

  • 146 |

    {word.wordNbr}.

    {word.hint}

    147 |
  • 148 | ); 149 | }); 150 | } 151 | 152 | convertPuzzleGridToPuzzleArray () { 153 | const submission = this.props.puzzleData.map((word) => { 154 | const startx = word.startx; 155 | const starty = word.starty; 156 | const length = word.word.length; 157 | const direction = word.wordOrientation; 158 | 159 | const enteredLetters = this.state.puzzleGrid.map((colData, row) => { 160 | return colData.map((cell, col) => { 161 | if (startx === col && starty === row) { 162 | return cell.currentValue || '*'; 163 | } 164 | 165 | for (let i = 1; i < length; i++) { 166 | if (direction === 'down' && startx === col && starty + i === row) { 167 | return cell.currentValue || '*'; 168 | } else if (direction === 'across' && startx + i === col && starty === row) { 169 | return cell.currentValue || '*'; 170 | } 171 | } 172 | }); 173 | }); 174 | 175 | const enteredValue = enteredLetters.reduce((arr, val) => { 176 | return arr.concat(val); 177 | }).join(''); 178 | 179 | return Object.assign({}, word, { 180 | enteredValue 181 | }); 182 | }); 183 | 184 | return submission; 185 | } 186 | 187 | reloadPuzzle () { 188 | this.props.puzzleActions.sendingData(); 189 | this.props.puzzleActions.getPuzzleData(); 190 | } 191 | 192 | clearPuzzle () { 193 | let submission = [...this.props.puzzleData]; 194 | submission = submission.map(obj => { 195 | const letters = obj.enteredValue.length; 196 | obj.enteredValue = new Array(letters).fill('*').join(''); 197 | return obj; 198 | }); 199 | 200 | this.props.puzzleActions.clearPuzzleData(submission); 201 | } 202 | 203 | submitPuzzle (e) { 204 | e.preventDefault(); 205 | const submission = this.convertPuzzleGridToPuzzleArray(); 206 | 207 | this.props.puzzleActions.submitPuzzleData(submission); 208 | } 209 | 210 | render () { 211 | return ( 212 |
    213 |
    214 |
    215 |
    216 | {this.state.cells} 217 |
    218 |
    219 | 220 | 221 | 222 |
    223 |
    224 |
    225 |
    226 |
    227 |
    228 |
    Down
    229 |
      230 | {this.state.downHints} 231 |
    232 |
    233 |
    234 |
    Across
    235 |
      236 | {this.state.acrossHints} 237 |
    238 |
    239 |
    240 |
    241 |
    242 | ); 243 | } 244 | } 245 | 246 | PuzzleComponent.propTypes = { 247 | puzzleActions: PropTypes.objectOf(PropTypes.func), 248 | state: PropTypes.object, 249 | puzzleData: PropTypes.array, 250 | puzzleId: PropTypes.string 251 | }; 252 | 253 | function mapStateToProps (state) { 254 | return { 255 | puzzleId: state.puzzle.id, 256 | puzzleData: state.puzzle.puzzleData 257 | }; 258 | } 259 | 260 | function mapDispatchToProps (dispatch) { 261 | return { 262 | puzzleActions: bindActionCreators(puzzleActions, dispatch) 263 | }; 264 | } 265 | 266 | export default connect(mapStateToProps, mapDispatchToProps)(PuzzleComponent); 267 | -------------------------------------------------------------------------------- /applications/kr8sswordz-pages/src/styles/utils/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Change the default font family in all browsers (opinionated). 5 | * 2. Prevent adjustments of font size after orientation changes in IE and iOS. 6 | */ 7 | 8 | html { 9 | font-family: sans-serif; /* 1 */ 10 | -ms-text-size-adjust: 100%; /* 2 */ 11 | -webkit-text-size-adjust: 100%; /* 2 */ 12 | } 13 | 14 | /** 15 | * Remove the margin in all browsers (opinionated). 16 | */ 17 | 18 | body { 19 | margin: 0; 20 | } 21 | 22 | /* HTML5 display definitions 23 | ========================================================================== */ 24 | 25 | /** 26 | * Add the correct display in IE 9-. 27 | * 1. Add the correct display in Edge, IE, and Firefox. 28 | * 2. Add the correct display in IE. 29 | */ 30 | 31 | article, 32 | aside, 33 | details, /* 1 */ 34 | figcaption, 35 | figure, 36 | footer, 37 | header, 38 | main, /* 2 */ 39 | menu, 40 | nav, 41 | section, 42 | summary { /* 1 */ 43 | display: block; 44 | } 45 | 46 | /** 47 | * Add the correct display in IE 9-. 48 | */ 49 | 50 | audio, 51 | canvas, 52 | progress, 53 | video { 54 | display: inline-block; 55 | } 56 | 57 | /** 58 | * Add the correct display in iOS 4-7. 59 | */ 60 | 61 | audio:not([controls]) { 62 | display: none; 63 | height: 0; 64 | } 65 | 66 | /** 67 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 68 | */ 69 | 70 | progress { 71 | vertical-align: baseline; 72 | } 73 | 74 | /** 75 | * Add the correct display in IE 10-. 76 | * 1. Add the correct display in IE. 77 | */ 78 | 79 | template, /* 1 */ 80 | [hidden] { 81 | display: none; 82 | } 83 | 84 | /* Links 85 | ========================================================================== */ 86 | 87 | /** 88 | * 1. Remove the gray background on active links in IE 10. 89 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 90 | */ 91 | 92 | a { 93 | background-color: transparent; /* 1 */ 94 | -webkit-text-decoration-skip: objects; /* 2 */ 95 | } 96 | 97 | /** 98 | * Remove the outline on focused links when they are also active or hovered 99 | * in all browsers (opinionated). 100 | */ 101 | 102 | a:active, 103 | a:hover { 104 | outline-width: 0; 105 | } 106 | 107 | /* Text-level semantics 108 | ========================================================================== */ 109 | 110 | /** 111 | * 1. Remove the bottom border in Firefox 39-. 112 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: none; /* 1 */ 117 | text-decoration: underline; /* 2 */ 118 | text-decoration: underline dotted; /* 2 */ 119 | } 120 | 121 | /** 122 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 123 | */ 124 | 125 | b, 126 | strong { 127 | font-weight: inherit; 128 | } 129 | 130 | /** 131 | * Add the correct font weight in Chrome, Edge, and Safari. 132 | */ 133 | 134 | b, 135 | strong { 136 | font-weight: bolder; 137 | } 138 | 139 | /** 140 | * Add the correct font style in Android 4.3-. 141 | */ 142 | 143 | dfn { 144 | font-style: italic; 145 | } 146 | 147 | /** 148 | * Correct the font size and margin on `h1` elements within `section` and 149 | * `article` contexts in Chrome, Firefox, and Safari. 150 | */ 151 | 152 | h1 { 153 | font-size: 2em; 154 | margin: 0.67em 0; 155 | } 156 | 157 | /** 158 | * Add the correct background and color in IE 9-. 159 | */ 160 | 161 | mark { 162 | background-color: #ff0; 163 | color: #000; 164 | } 165 | 166 | /** 167 | * Add the correct font size in all browsers. 168 | */ 169 | 170 | small { 171 | font-size: 80%; 172 | } 173 | 174 | /** 175 | * Prevent `sub` and `sup` elements from affecting the line height in 176 | * all browsers. 177 | */ 178 | 179 | sub, 180 | sup { 181 | font-size: 75%; 182 | line-height: 0; 183 | position: relative; 184 | vertical-align: baseline; 185 | } 186 | 187 | sub { 188 | bottom: -0.25em; 189 | } 190 | 191 | sup { 192 | top: -0.5em; 193 | } 194 | 195 | /* Embedded content 196 | ========================================================================== */ 197 | 198 | /** 199 | * Remove the border on images inside links in IE 10-. 200 | */ 201 | 202 | img { 203 | border-style: none; 204 | } 205 | 206 | /* Grouping content 207 | ========================================================================== */ 208 | 209 | /** 210 | * 1. Correct the inheritance and scaling of font size in all browsers. 211 | * 2. Correct the odd `em` font sizing in all browsers. 212 | */ 213 | 214 | code, 215 | kbd, 216 | pre, 217 | samp { 218 | font-family: monospace, monospace; /* 1 */ 219 | font-size: 1em; /* 2 */ 220 | } 221 | 222 | /** 223 | * Add the correct margin in IE 8. 224 | */ 225 | 226 | figure { 227 | margin: 1em 40px; 228 | } 229 | 230 | /** 231 | * 1. Add the correct box sizing in Firefox. 232 | * 2. Show the overflow in Edge and IE. 233 | */ 234 | 235 | hr { 236 | box-sizing: content-box; /* 1 */ 237 | height: 0; /* 1 */ 238 | overflow: visible; /* 2 */ 239 | } 240 | 241 | /* Forms 242 | ========================================================================== */ 243 | 244 | /** 245 | * 1. Change font properties to `inherit` in all browsers (opinionated). 246 | * 2. Remove the margin in Firefox and Safari. 247 | */ 248 | 249 | button, 250 | input, 251 | select, 252 | textarea { 253 | font: inherit; /* 1 */ 254 | margin: 0; /* 2 */ 255 | } 256 | 257 | /** 258 | * Restore the font weight unset by the previous rule. 259 | */ 260 | 261 | optgroup { 262 | font-weight: bold; 263 | } 264 | 265 | /** 266 | * Show the overflow in IE. 267 | * 1. Show the overflow in Edge. 268 | */ 269 | 270 | button, 271 | input { /* 1 */ 272 | overflow: visible; 273 | } 274 | 275 | /** 276 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 277 | * 1. Remove the inheritance of text transform in Firefox. 278 | */ 279 | 280 | button, 281 | select { /* 1 */ 282 | text-transform: none; 283 | } 284 | 285 | /** 286 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 287 | * controls in Android 4. 288 | * 2. Correct the inability to style clickable types in iOS and Safari. 289 | */ 290 | 291 | button, 292 | html [type="button"], /* 1 */ 293 | [type="reset"], 294 | [type="submit"] { 295 | -webkit-appearance: button; /* 2 */ 296 | } 297 | 298 | /** 299 | * Remove the inner border and padding in Firefox. 300 | */ 301 | 302 | button::-moz-focus-inner, 303 | [type="button"]::-moz-focus-inner, 304 | [type="reset"]::-moz-focus-inner, 305 | [type="submit"]::-moz-focus-inner { 306 | border-style: none; 307 | padding: 0; 308 | } 309 | 310 | /** 311 | * Restore the focus styles unset by the previous rule. 312 | */ 313 | 314 | button:-moz-focusring, 315 | [type="button"]:-moz-focusring, 316 | [type="reset"]:-moz-focusring, 317 | [type="submit"]:-moz-focusring { 318 | outline: 1px dotted ButtonText; 319 | } 320 | 321 | /** 322 | * Change the border, margin, and padding in all browsers (opinionated). 323 | */ 324 | 325 | fieldset { 326 | border: 1px solid #c0c0c0; 327 | margin: 0 2px; 328 | padding: 0.35em 0.625em 0.75em; 329 | } 330 | 331 | /** 332 | * 1. Correct the text wrapping in Edge and IE. 333 | * 2. Correct the color inheritance from `fieldset` elements in IE. 334 | * 3. Remove the padding so developers are not caught out when they zero out 335 | * `fieldset` elements in all browsers. 336 | */ 337 | 338 | legend { 339 | box-sizing: border-box; /* 1 */ 340 | color: inherit; /* 2 */ 341 | display: table; /* 1 */ 342 | max-width: 100%; /* 1 */ 343 | padding: 0; /* 3 */ 344 | white-space: normal; /* 1 */ 345 | } 346 | 347 | /** 348 | * Remove the default vertical scrollbar in IE. 349 | */ 350 | 351 | textarea { 352 | overflow: auto; 353 | } 354 | 355 | /** 356 | * 1. Add the correct box sizing in IE 10-. 357 | * 2. Remove the padding in IE 10-. 358 | */ 359 | 360 | [type="checkbox"], 361 | [type="radio"] { 362 | box-sizing: border-box; /* 1 */ 363 | padding: 0; /* 2 */ 364 | } 365 | 366 | /** 367 | * Correct the cursor style of increment and decrement buttons in Chrome. 368 | */ 369 | 370 | [type="number"]::-webkit-inner-spin-button, 371 | [type="number"]::-webkit-outer-spin-button { 372 | height: auto; 373 | } 374 | 375 | /** 376 | * 1. Correct the odd appearance in Chrome and Safari. 377 | * 2. Correct the outline style in Safari. 378 | */ 379 | 380 | [type="search"] { 381 | -webkit-appearance: textfield; /* 1 */ 382 | outline-offset: -2px; /* 2 */ 383 | } 384 | 385 | /** 386 | * Remove the inner padding and cancel buttons in Chrome and Safari on OS X. 387 | */ 388 | 389 | [type="search"]::-webkit-search-cancel-button, 390 | [type="search"]::-webkit-search-decoration { 391 | -webkit-appearance: none; 392 | } 393 | 394 | /** 395 | * Correct the text style of placeholders in Chrome, Edge, and Safari. 396 | */ 397 | 398 | ::-webkit-input-placeholder { 399 | color: inherit; 400 | opacity: 0.54; 401 | } 402 | 403 | /** 404 | * 1. Correct the inability to style clickable types in iOS and Safari. 405 | * 2. Change font properties to `inherit` in Safari. 406 | */ 407 | 408 | ::-webkit-file-upload-button { 409 | -webkit-appearance: button; /* 1 */ 410 | font: inherit; /* 2 */ 411 | } 412 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 Kenzan, LLC 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------