├── .dockerignore ├── .gitattributes ├── .gitignore ├── .helmignore ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── Makefile ├── NOTICE ├── OWNERS ├── OWNERS_ALIASES ├── README.md ├── charts ├── croc-hunter-jenkinsx │ ├── .helmignore │ ├── Chart.yaml │ ├── Makefile │ ├── README.md │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── canary.yaml │ │ ├── deployment.yaml │ │ └── service.yaml │ └── values.yaml └── preview │ ├── Chart.yaml │ ├── Makefile │ ├── requirements.yaml │ └── values.yaml ├── croc-hunter.go ├── curlloop.sh ├── flagger ├── README.md └── croc-hunter-canary.yaml ├── istio ├── README.md ├── gateway.yaml ├── google-api.yaml └── virtualservice.yaml ├── shipper ├── README.md ├── croc-hunter-ingress-global.yaml └── croc-hunter.yaml ├── skaffold.yaml ├── static ├── favicon-16x16.png ├── favicon-32x32.png ├── game.css ├── game.js ├── game2.js ├── sprite.png └── sprite2.png └── watch.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | draft.toml 2 | target/classes 3 | target/generated-sources 4 | target/generated-test-sources 5 | target/maven-archiver 6 | target/maven-status 7 | target/surefire-reports 8 | target/test-classes 9 | target/*.original 10 | charts/ 11 | NOTICE 12 | LICENSE 13 | README.md -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | Makefile linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | .idea 4 | .cache 5 | .DS_Store 6 | *.im? 7 | target 8 | work 9 | bin/ -------------------------------------------------------------------------------- /.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 | *.png 23 | 24 | # known compile time folders 25 | target/ 26 | node_modules/ 27 | vendor/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | ARG GIT_SHA 3 | ARG WORKFLOW_RELEASE 4 | ENV GIT_SHA=$GIT_SHA 5 | ENV WORKFLOW_RELEASE=$WORKFLOW_RELEASE 6 | ENV POWERED_BY=Jenkins-X 7 | EXPOSE 8080 8 | ENTRYPOINT ["/croc-hunter-jenkinsx"] 9 | COPY ./bin/ / 10 | COPY static/ static/ 11 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | environment { 4 | ORG = 'carlossg' 5 | APP_NAME = 'croc-hunter-jenkinsx' 6 | GIT_PROVIDER = 'github.com' 7 | CHARTMUSEUM_CREDS = credentials('jenkins-x-chartmuseum') 8 | SKAFFOLD_UPDATE_CHECK = 'false' 9 | } 10 | stages { 11 | stage('CI Build and push snapshot') { 12 | when { 13 | branch 'PR-*' 14 | } 15 | environment { 16 | PREVIEW_VERSION = "0.0.0-SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER" 17 | PREVIEW_NAMESPACE = "$APP_NAME-$BRANCH_NAME".toLowerCase() 18 | HELM_RELEASE = "$PREVIEW_NAMESPACE".toLowerCase() 19 | } 20 | steps { 21 | dir ('/home/jenkins/go/src/github.com/carlossg/croc-hunter-jenkinsx-serverless') { 22 | checkout scm 23 | sh "make VERSION=\$PREVIEW_VERSION GIT_COMMIT=\$PULL_PULL_SHA linux" 24 | sh 'export VERSION=$PREVIEW_VERSION && skaffold build -f skaffold.yaml.new' 25 | 26 | sh "jx step validate --min-jx-version 1.2.36" 27 | sh "jx step post build --image \$DOCKER_REGISTRY/$ORG/$APP_NAME:$PREVIEW_VERSION" 28 | } 29 | dir ('/home/jenkins/go/src/github.com/carlossg/croc-hunter-jenkinsx-serverless/charts/preview') { 30 | sh "make preview" 31 | sh "jx preview --app $APP_NAME --dir ../.." 32 | } 33 | } 34 | } 35 | stage('Build Release') { 36 | when { 37 | branch 'master' 38 | } 39 | steps { 40 | dir ('/home/jenkins/go/src/github.com/carlossg/croc-hunter-jenkinsx-serverless') { 41 | checkout scm 42 | } 43 | dir ('/home/jenkins/go/src/github.com/carlossg/croc-hunter-jenkinsx-serverless/charts/croc-hunter-jenkinsx') { 44 | // until we switch to the new kubernetes / jenkins credential implementation use git credentials store 45 | sh "git config --global credential.helper store" 46 | sh "jx step validate --min-jx-version 1.1.73" 47 | sh "jx step git credentials" 48 | } 49 | dir ('/home/jenkins/go/src/github.com/carlossg/croc-hunter-jenkinsx-serverless') { 50 | // so we can retrieve the version in later steps 51 | sh "echo \$(jx-release-version) > VERSION" 52 | } 53 | dir ('/home/jenkins/go/src/github.com/carlossg/croc-hunter-jenkinsx-serverless/charts/croc-hunter-jenkinsx') { 54 | sh "make tag" 55 | } 56 | dir ('/home/jenkins/go/src/github.com/carlossg/croc-hunter-jenkinsx-serverless') { 57 | sh "make VERSION=`cat VERSION` GIT_COMMIT=\$PULL_BASE_SHA build" 58 | sh "export VERSION=`cat VERSION` && skaffold build -f skaffold.yaml.new" 59 | sh "jx step validate --min-jx-version 1.2.36" 60 | sh "jx step post build --image $DOCKER_REGISTRY/$ORG/$APP_NAME:\$(cat VERSION)" 61 | } 62 | } 63 | } 64 | stage('Promote to Environments') { 65 | when { 66 | branch 'master' 67 | } 68 | steps { 69 | dir ('/home/jenkins/go/src/github.com/carlossg/croc-hunter-jenkinsx-serverless/charts/croc-hunter-jenkinsx') { 70 | sh 'jx step changelog --version v\$(cat ../../VERSION) --generate-yaml=false' 71 | 72 | // release the helm chart 73 | sh 'make release' 74 | 75 | // promote through all 'Auto' promotion Environments 76 | sh 'jx promote -b --all-auto --timeout 1h --version \$(cat ../../VERSION) --no-wait' 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2018 Lachlan Evenson, Carlos Sanchez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | GO := GO15VENDOREXPERIMENT=1 go 3 | NAME := croc-hunter-jenkinsx 4 | OS := $(shell uname) 5 | MAIN_GO := croc-hunter.go 6 | ROOT_PACKAGE := $(GIT_PROVIDER)/$(ORG)/$(NAME) 7 | GO_VERSION := $(shell $(GO) version | sed -e 's/^[^0-9.]*\([0-9.]*\).*/\1/') 8 | PACKAGE_DIRS := $(shell $(GO) list ./... | grep -v /vendor/) 9 | PKGS := $(shell go list ./... | grep -v /vendor | grep -v generated) 10 | PKGS := $(subst :,_,$(PKGS)) 11 | BUILDFLAGS := '' 12 | CGO_ENABLED = 0 13 | VENDOR_DIR=vendor 14 | 15 | all: build 16 | 17 | check: fmt build test 18 | 19 | build: skaffold.yaml.new 20 | CGO_ENABLED=$(CGO_ENABLED) $(GO) build -ldflags $(BUILDFLAGS) -o bin/$(NAME) $(MAIN_GO) 21 | 22 | test: 23 | CGO_ENABLED=$(CGO_ENABLED) $(GO) test $(PACKAGE_DIRS) -test.v 24 | 25 | full: $(PKGS) 26 | 27 | install: 28 | GOBIN=${GOPATH}/bin $(GO) install -ldflags $(BUILDFLAGS) $(MAIN_GO) 29 | 30 | fmt: 31 | @FORMATTED=`$(GO) fmt $(PACKAGE_DIRS)` 32 | @([[ ! -z "$(FORMATTED)" ]] && printf "Fixed unformatted files:\n$(FORMATTED)") || true 33 | 34 | clean: 35 | rm -rf build release 36 | 37 | linux: skaffold.yaml.new 38 | CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 $(GO) build -ldflags $(BUILDFLAGS) -o bin/$(NAME) $(MAIN_GO) 39 | 40 | .PHONY: release clean 41 | 42 | FGT := $(GOPATH)/bin/fgt 43 | $(FGT): 44 | go get github.com/GeertJohan/fgt 45 | 46 | GOLINT := $(GOPATH)/bin/golint 47 | $(GOLINT): 48 | go get github.com/golang/lint/golint 49 | 50 | $(PKGS): $(GOLINT) $(FGT) 51 | @echo "LINTING" 52 | @$(FGT) $(GOLINT) $(GOPATH)/src/$@/*.go 53 | @echo "VETTING" 54 | @go vet -v $@ 55 | @echo "TESTING" 56 | @go test -v $@ 57 | 58 | skaffold.yaml.new: 59 | cp skaffold.yaml skaffold.yaml.new 60 | ifeq ($(OS),Darwin) 61 | sed -i "" -e "s/{{.VERSION}}/x$(VERSION)/" skaffold.yaml.new 62 | sed -i "" -e "s/{{.GIT_COMMIT}}/$(GIT_COMMIT)/" skaffold.yaml.new 63 | else ifeq ($(OS),Linux) 64 | sed -i -e "s/{{.VERSION}}/$(VERSION)/" skaffold.yaml.new 65 | sed -i -e "s/{{.GIT_COMMIT}}/$(GIT_COMMIT)/" skaffold.yaml.new 66 | else 67 | echo "platfrom $(OS) not supported to release from" 68 | exit -1 69 | endif 70 | 71 | 72 | .PHONY: lint 73 | lint: vendor | $(PKGS) $(GOLINT) # ❷ 74 | @cd $(BASE) && ret=0 && for pkg in $(PKGS); do \ 75 | test -z "$$($(GOLINT) $$pkg | tee /dev/stderr)" || ret=1 ; \ 76 | done ; exit $$ret 77 | 78 | watch: 79 | reflex -r "\.go$" -R "vendor.*" make skaffold-run 80 | 81 | skaffold-run: build 82 | skaffold run -p dev 83 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | MIT License: 2 | 3 | Copyright (C) 2011-2015 Heroku, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | approvers: 2 | - carlossg 3 | reviewers: 4 | - carlossg -------------------------------------------------------------------------------- /OWNERS_ALIASES: -------------------------------------------------------------------------------- 1 | aliases: 2 | - carlossg 3 | best-approvers: 4 | - carlossg 5 | best-reviewers: 6 | - carlossg 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Croc Hunter - The game! 2 | 3 | For those that have dreamt to hunt crocs 4 | 5 | # Usage 6 | 7 | Basic go webserver to demonstrate example CI/CD pipeline using Kubernetes 8 | 9 | # Deploy using JenkinsX (Kubernetes, Helm, Monocular, ChartMuseum) 10 | 11 | Just follow the [JenkinsX](http://jenkins-x.io) installation with `--prow=true` 12 | 13 | For example, if using GKE with cert-manager preinstalled for https certificates 14 | 15 | jx install --provider=gke --domain=eu.g.csanchez.org --prow 16 | jx upgrade ingress 17 | 18 | Then fork this repo and [import it](http://jenkins-x.io/developing/import/) 19 | 20 | jx import --url https://github.com/GITHUB_USER/croc-hunter-jenkinsx-serverless --no-draft --pack=go 21 | 22 | Then, any PRs against this repo will be automatically deployed to preview environments. 23 | When they are merged they will be deployed to the `staging` environment. 24 | 25 | To tail all the build logs 26 | 27 | kail -l build.knative.dev/buildName --since=5m 28 | 29 | Or in [GKE StackDriver logs](https://console.cloud.google.com/logs/viewer?authuser=1&advancedFilter=resource.type%3D%22container%22%0Aresource.labels.cluster_name%3D%22samurainarrow%22%0Aresource.labels.container_name%3Dbuild-step-jenkins) 30 | 31 | ``` 32 | resource.type="container" 33 | resource.labels.cluster_name="samurainarrow" 34 | resource.labels.container_name="build-step-jenkins" 35 | ``` 36 | 37 | To [promote from staging to production](http://jenkins-x.io/developing/promote/) just run 38 | 39 | jx promote croc-hunter-jenkinsx --version 0.0.1 --env production 40 | 41 | Then delete the PR environments 42 | 43 | jx delete env 44 | 45 | # Acknowledgements 46 | 47 | Original work by [Lachlan Evenson](https://github.com/lachie83/croc-hunter) 48 | Continuation of the awesome work by everett-toews. 49 | * https://gist.github.com/everett-toews/ed56adcfd525ce65b178d2e5a5eb06aa 50 | 51 | ## Watch Their Demo 52 | 53 | https://www.youtube.com/watch?v=eMOzF_xAm7w 54 | -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/.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 | -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: A Helm chart for Kubernetes 3 | icon: https://raw.githubusercontent.com/jenkins-x/jenkins-x-platform/d273e09/images/go.png 4 | name: croc-hunter-jenkinsx 5 | version: 0.1.0-SNAPSHOT 6 | -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/Makefile: -------------------------------------------------------------------------------- 1 | CHART_REPO := http://jenkins-x-chartmuseum:8080 2 | CURRENT=$(pwd) 3 | NAME := croc-hunter-jenkinsx 4 | OS := $(shell uname) 5 | VERSION := $(shell cat ../../VERSION) 6 | 7 | build: clean 8 | rm -rf requirements.lock 9 | helm dependency build 10 | helm lint 11 | 12 | install: clean build 13 | helm install . --name ${NAME} 14 | 15 | upgrade: clean build 16 | helm upgrade ${NAME} . 17 | 18 | delete: 19 | helm delete --purge ${NAME} 20 | 21 | clean: 22 | rm -rf charts 23 | rm -rf ${NAME}*.tgz 24 | 25 | release: clean 26 | helm dependency build 27 | helm lint 28 | helm init --client-only 29 | helm package . 30 | curl --fail -u $(CHARTMUSEUM_CREDS_USR):$(CHARTMUSEUM_CREDS_PSW) --data-binary "@$(NAME)-$(shell sed -n 's/^version: //p' Chart.yaml).tgz" $(CHART_REPO)/api/charts 31 | rm -rf ${NAME}*.tgz% 32 | 33 | tag: 34 | ifeq ($(OS),Darwin) 35 | sed -i "" -e "s/version:.*/version: $(VERSION)/" Chart.yaml 36 | sed -i "" -e "s/tag: .*/tag: $(VERSION)/" values.yaml 37 | else ifeq ($(OS),Linux) 38 | sed -i -e "s/version:.*/version: $(VERSION)/" Chart.yaml 39 | sed -i -e "s|repository: .*|repository: $(DOCKER_REGISTRY)\/carlossg\/$(NAME)|" values.yaml 40 | sed -i -e "s/tag: .*/tag: $(VERSION)/" values.yaml 41 | else 42 | echo "platfrom $(OS) not supported to tag with" 43 | exit -1 44 | endif 45 | git add --all 46 | git commit -m "release $(VERSION)" --allow-empty # if first release then no verion update is performed 47 | git tag -fa v$(VERSION) -m "Release version $(VERSION)" 48 | git push origin v$(VERSION) -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/README.md: -------------------------------------------------------------------------------- 1 | # golang application -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 2 | Get the application URL by running these commands: 3 | 4 | kubectl get ingress {{ template "fullname" . }} 5 | -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/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 | -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/templates/canary.yaml: -------------------------------------------------------------------------------- 1 | {{- if eq .Release.Namespace "jx-production" }} 2 | {{- if .Values.canary.enable }} 3 | apiVersion: flagger.app/v1alpha2 4 | kind: Canary 5 | metadata: 6 | # canary name must match deployment name 7 | name: {{ template "fullname" . }} 8 | spec: 9 | # deployment reference 10 | targetRef: 11 | apiVersion: apps/v1 12 | kind: Deployment 13 | name: {{ template "fullname" . }} 14 | progressDeadlineSeconds: 60 15 | service: 16 | # container port 17 | port: {{.Values.service.internalPort}} 18 | {{- if .Values.canary.service.gateways }} 19 | # Istio gateways (optional) 20 | gateways: 21 | {{ toYaml .Values.canary.service.gateways | indent 4 }} 22 | {{- end }} 23 | {{- if .Values.canary.service.hosts }} 24 | # Istio virtual service host names (optional) 25 | hosts: 26 | {{ toYaml .Values.canary.service.hosts | indent 4 }} 27 | {{- end }} 28 | canaryAnalysis: 29 | # schedule interval (default 60s) 30 | interval: {{ .Values.canary.canaryAnalysis.interval }} 31 | # max number of failed metric checks before rollback 32 | threshold: {{ .Values.canary.canaryAnalysis.threshold }} 33 | # max traffic percentage routed to canary 34 | # percentage (0-100) 35 | maxWeight: {{ .Values.canary.canaryAnalysis.maxWeight }} 36 | # canary increment step 37 | # percentage (0-100) 38 | stepWeight: {{ .Values.canary.canaryAnalysis.stepWeight }} 39 | {{- if .Values.canary.canaryAnalysis.metrics }} 40 | metrics: 41 | {{ toYaml .Values.canary.canaryAnalysis.metrics | indent 4 }} 42 | {{- end }} 43 | {{- end }} 44 | {{- end }} 45 | -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | labels: 6 | draft: {{ default "draft-app" .Values.draft }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" 8 | spec: 9 | replicas: {{ .Values.replicaCount }} 10 | selector: 11 | matchLabels: 12 | draft: {{ default "draft-app" .Values.draft }} 13 | {{- if .Values.appLabel }} 14 | app: {{ .Values.appLabel }} 15 | {{- else }} 16 | app: {{ template "fullname" . }} 17 | {{- end }} 18 | template: 19 | metadata: 20 | labels: 21 | draft: {{ default "draft-app" .Values.draft }} 22 | {{- if .Values.appLabel }} 23 | app: {{ .Values.appLabel }} 24 | {{- else }} 25 | app: {{ template "fullname" . }} 26 | {{- end }} 27 | {{- if .Values.podAnnotations }} 28 | annotations: 29 | {{ toYaml .Values.podAnnotations | indent 8 }} 30 | {{- end }} 31 | spec: 32 | containers: 33 | - name: {{ .Chart.Name }} 34 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 35 | imagePullPolicy: {{ .Values.image.pullPolicy }} 36 | ports: 37 | - containerPort: {{ .Values.service.internalPort }} 38 | livenessProbe: 39 | httpGet: 40 | path: {{ .Values.probePath }} 41 | port: {{ .Values.service.internalPort }} 42 | initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} 43 | periodSeconds: {{ .Values.livenessProbe.periodSeconds }} 44 | successThreshold: {{ .Values.livenessProbe.successThreshold }} 45 | timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} 46 | readinessProbe: 47 | httpGet: 48 | path: {{ .Values.probePath }} 49 | port: {{ .Values.service.internalPort }} 50 | periodSeconds: {{ .Values.readinessProbe.periodSeconds }} 51 | successThreshold: {{ .Values.readinessProbe.successThreshold }} 52 | timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} 53 | resources: 54 | {{ toYaml .Values.resources | indent 12 }} 55 | terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} 56 | -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | {{- if .Values.service.name }} 5 | name: {{ .Values.service.name }} 6 | {{- else }} 7 | name: {{ template "fullname" . }} 8 | {{- end }} 9 | labels: 10 | chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}" 11 | {{- if .Values.service.annotations }} 12 | annotations: 13 | {{ toYaml .Values.service.annotations | indent 4 }} 14 | {{- end }} 15 | spec: 16 | type: {{ .Values.service.type }} 17 | ports: 18 | - port: {{ .Values.service.externalPort }} 19 | targetPort: {{ .Values.service.internalPort }} 20 | protocol: TCP 21 | name: http 22 | {{- if .Values.service.nodePort }} 23 | nodePort: {{ .Values.service.nodePort }} 24 | {{- end }} 25 | selector: 26 | {{- if .Values.appLabel }} 27 | app: {{ .Values.appLabel }} 28 | {{- else }} 29 | app: {{ template "fullname" . }} 30 | {{- end }} 31 | -------------------------------------------------------------------------------- /charts/croc-hunter-jenkinsx/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for Go projects. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | replicaCount: 1 5 | image: 6 | repository: draft 7 | tag: dev 8 | pullPolicy: IfNotPresent 9 | service: 10 | name: croc-hunter-jenkinsx 11 | type: ClusterIP 12 | externalPort: 80 13 | internalPort: 8080 14 | annotations: 15 | fabric8.io/expose: "true" 16 | fabric8.io/ingress.annotations: "kubernetes.io/ingress.class: nginx" 17 | resources: 18 | limits: 19 | cpu: 100m 20 | memory: 256Mi 21 | requests: 22 | cpu: 80m 23 | memory: 128Mi 24 | probePath: / 25 | livenessProbe: 26 | initialDelaySeconds: 60 27 | periodSeconds: 10 28 | successThreshold: 1 29 | timeoutSeconds: 1 30 | readinessProbe: 31 | periodSeconds: 10 32 | successThreshold: 1 33 | timeoutSeconds: 1 34 | terminationGracePeriodSeconds: 10 35 | canary: 36 | enable: true 37 | service: 38 | hosts: 39 | - croc-hunter.istio.us.g.csanchez.org 40 | - croc-hunter.istio.eu.g.csanchez.org 41 | gateways: 42 | - jx-gateway.istio-system.svc.cluster.local 43 | canaryAnalysis: 44 | interval: 60s 45 | threshold: 3 46 | maxWeight: 50 47 | stepWeight: 10 48 | metrics: 49 | - name: istio_requests_total 50 | # minimum req success rate (non 5xx responses) 51 | # percentage (0-100) 52 | threshold: 99 53 | interval: 60s 54 | - name: istio_request_duration_seconds_bucket 55 | # maximum req duration P99 56 | # milliseconds 57 | threshold: 500 58 | interval: 60s 59 | -------------------------------------------------------------------------------- /charts/preview/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: A Helm chart for Kubernetes 3 | icon: https://raw.githubusercontent.com/jenkins-x/jenkins-x-platform/master/images/go.png 4 | name: preview 5 | version: 0.1.0-SNAPSHOT 6 | -------------------------------------------------------------------------------- /charts/preview/Makefile: -------------------------------------------------------------------------------- 1 | OS := $(shell uname) 2 | 3 | preview: 4 | ifeq ($(OS),Darwin) 5 | sed -i "" -e "s/version:.*/version: $(PREVIEW_VERSION)/" Chart.yaml 6 | sed -i "" -e "s/version:.*/version: $(PREVIEW_VERSION)/" ../*/Chart.yaml 7 | sed -i "" -e "s/tag: .*/tag: $(PREVIEW_VERSION)/" values.yaml 8 | else ifeq ($(OS),Linux) 9 | sed -i -e "s/version:.*/version: $(PREVIEW_VERSION)/" Chart.yaml 10 | sed -i -e "s/version:.*/version: $(PREVIEW_VERSION)/" ../*/Chart.yaml 11 | sed -i -e "s|repository: .*|repository: $(DOCKER_REGISTRY)\/carlossg\/golang-http|" values.yaml 12 | sed -i -e "s/tag: .*/tag: $(PREVIEW_VERSION)/" values.yaml 13 | else 14 | echo "platfrom $(OS) not supported to release from" 15 | exit -1 16 | endif 17 | echo " version: $(PREVIEW_VERSION)" >> requirements.yaml 18 | jx step helm build 19 | -------------------------------------------------------------------------------- /charts/preview/requirements.yaml: -------------------------------------------------------------------------------- 1 | 2 | dependencies: 3 | - alias: expose 4 | name: exposecontroller 5 | repository: http://chartmuseum.jenkins-x.io 6 | version: 2.3.89 7 | - alias: cleanup 8 | name: exposecontroller 9 | repository: http://chartmuseum.jenkins-x.io 10 | version: 2.3.89 11 | - alias: preview 12 | name: croc-hunter-jenkinsx 13 | repository: file://../croc-hunter-jenkinsx 14 | -------------------------------------------------------------------------------- /charts/preview/values.yaml: -------------------------------------------------------------------------------- 1 | 2 | expose: 3 | Annotations: 4 | helm.sh/hook: post-install,post-upgrade 5 | helm.sh/hook-delete-policy: hook-succeeded 6 | config: 7 | exposer: Ingress 8 | http: true 9 | tlsacme: false 10 | 11 | cleanup: 12 | Args: 13 | - --cleanup 14 | Annotations: 15 | helm.sh/hook: pre-delete 16 | helm.sh/hook-delete-policy: hook-succeeded 17 | 18 | preview: 19 | image: 20 | repository: 21 | tag: 22 | pullPolicy: IfNotPresent -------------------------------------------------------------------------------- /croc-hunter.go: -------------------------------------------------------------------------------- 1 | // The infamous "croc-hunter" game as featured at many a demo 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | "time" 13 | ) 14 | 15 | var release = os.Getenv("WORKFLOW_RELEASE") 16 | var commit = os.Getenv("GIT_SHA") 17 | var powered = os.Getenv("POWERED_BY") 18 | var region = "" 19 | 20 | func main() { 21 | httpListenAddr := flag.String("port", "8080", "HTTP Listen address.") 22 | 23 | flag.Parse() 24 | 25 | log.Println("Starting server...") 26 | 27 | log.Println("release: " + release) 28 | log.Println("commit: " + commit) 29 | log.Println("powered: " + powered) 30 | 31 | if release == "" { 32 | release = "unknown" 33 | } 34 | if commit == "" { 35 | commit = "not present" 36 | } 37 | if powered == "" { 38 | powered = "deis" 39 | } 40 | // get region 41 | 42 | req, err := http.NewRequest("GET", "http://metadata.google.internal/computeMetadata/v1/instance/attributes/cluster-location", nil) 43 | if err == nil { 44 | req.Header.Set("Metadata-Flavor", "Google") 45 | resp, err := http.DefaultClient.Do(req) 46 | if err != nil { 47 | log.Printf("could not get region: %s", err) 48 | } 49 | if resp.StatusCode < 200 || resp.StatusCode >= 300 { 50 | log.Printf("could not get region: %s", http.StatusText(resp.StatusCode)) 51 | } 52 | body, err := ioutil.ReadAll(resp.Body) 53 | resp.Body.Close() 54 | if err != nil { 55 | log.Printf("could not read region response: %s", err) 56 | } else { 57 | region = string(body) 58 | } 59 | } else { 60 | log.Printf("could not build region request: %s", err) 61 | } 62 | log.Printf("region: %s", region) 63 | 64 | // point / at the handler function 65 | http.HandleFunc("/", handler) 66 | 67 | // serve static content from /static 68 | http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/")))) 69 | 70 | log.Println("Server started. Listening on port " + *httpListenAddr) 71 | log.Fatal(http.ListenAndServe(":"+*httpListenAddr, nil)) 72 | } 73 | 74 | const ( 75 | html = ` 76 | 77 | 78 | 79 | Croc Hunter 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
91 | Hostname: %s
92 | Region: %s
93 | Release: %s
94 | Commit: %s
95 | Powered By: %s
96 |
97 | 98 | 99 | ` 100 | ) 101 | 102 | func handler(w http.ResponseWriter, r *http.Request) { 103 | 104 | if r.URL.Path == "/healthz" { 105 | w.WriteHeader(http.StatusOK) 106 | return 107 | } 108 | 109 | if r.URL.Path == "/delay" { 110 | delay, _ := strconv.Atoi(r.URL.Query().Get("wait")) 111 | if delay <= 0 { 112 | delay = 10 113 | } 114 | time.Sleep(time.Duration(delay) * time.Second) 115 | w.WriteHeader(http.StatusOK) 116 | fmt.Fprintf(w, "{delay: %d}", delay) 117 | return 118 | } 119 | 120 | if r.URL.Path == "/status" { 121 | code, _ := strconv.Atoi(r.URL.Query().Get("code")) 122 | w.WriteHeader(code) 123 | fmt.Fprintf(w, "{code: %d}", code) 124 | return 125 | } 126 | 127 | hostname, err := os.Hostname() 128 | if err != nil { 129 | log.Printf("could not get hostname: %s", err) 130 | } 131 | 132 | fmt.Fprintf(w, html, hostname, region, release, commit, powered) 133 | } 134 | -------------------------------------------------------------------------------- /curlloop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export APP="$1" 4 | echo "curling URL $APP in a loop..." 5 | 6 | while true 7 | do 8 | curl $APP 9 | sleep 2 10 | done 11 | -------------------------------------------------------------------------------- /flagger/README.md: -------------------------------------------------------------------------------- 1 | # Canary Deployments with Flagger 2 | 3 | ## Installation 4 | 5 | Install Istio, Prometheus and [Flagger](https://docs.flagger.app) 6 | 7 | jx create addon istio 8 | jx create addon prometheus 9 | jx create addon flagger 10 | 11 | Istio is enabled in the `jx-production` namespace for metrics gathering. 12 | 13 | Get the ip of the Istio ingress and point your wildcard domain to it 14 | 15 | kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 16 | 17 | ## Application Configuration 18 | 19 | Add the [canary object](../charts/croc-hunter-jenkinsx/templates/canary.yaml) that will add our deployment to Flagger. Add it to the Helm chart so it is created when promoting to `jx-production` namespace. 20 | 21 | Update the `values.yaml` section `canary.service.hosts` with the hostname for your aplication. 22 | 23 | Optional: Create a `ServiceEntry` to allow traffic to the Google metadata api to display the region 24 | 25 | kubectl create -f ../istio/google-api.yaml 26 | 27 | ## Grafana Dashboard 28 | 29 | kubectl --namespace istio-system port-forward deploy/flagger-grafana 3000 30 | 31 | Access it at [http://localhost:3000](http://localhost:3000) using admin/admin 32 | Go to the `canary-analysis` dashboard and select 33 | 34 | * namespace: `jx-production` 35 | * primary: `jx-production-croc-hunter-jenkinsx-primary` 36 | * canary: `jx-production-croc-hunter-jenkinsx` 37 | 38 | to see the roll out metrics. 39 | 40 | ## Prometheus Metrics 41 | 42 | kubectl --namespace istio-system port-forward deploy/prometheus 9090 43 | 44 | ## Caveats 45 | 46 | If a rollback happens automatically because the metrics fail the GitOps repository for the production environment becomes out of date, still pointing to the new version instead of the old one. 47 | 48 | # Croc Hunter Demo 49 | 50 | Promote to production 51 | 52 | jx promote croc-hunter-jenkinsx --env production --version 0.0.xxx 53 | 54 | Tail the flagger logs 55 | 56 | kail -d flagger --since=5m 57 | 58 | Generate traffic to show the version served 59 | 60 | while true; do 61 | out=$(curl -sSL -w "%{http_code}" http://croc-hunter.istio.us.g.csanchez.org/) 62 | date="$(date +%R:%S)"; echo -n $date 63 | echo -n "$out" | tail -n 1 ; echo -n "-" ; echo "$out" | grep Release | grep -o '\d*\.\d*\.\d*' 64 | done 65 | 66 | Generate delays and errors to show automatic rollbacks 67 | 68 | From a pod in the cluster (ie. a jx devpod) run 69 | 70 | watch curl -sSL http://jx-production-croc-hunter-jenkinsx-canary.jx-production.svc.cluster.local:8080/delay?wait=5 71 | watch curl -sSL http://jx-production-croc-hunter-jenkinsx-canary.jx-production.svc.cluster.local:8080/status?code=500 72 | -------------------------------------------------------------------------------- /flagger/croc-hunter-canary.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: flagger.app/v1alpha2 2 | kind: Canary 3 | metadata: 4 | # canary name must match deployment name 5 | name: jx-production-croc-hunter-jenkinsx 6 | namespace: jx-production 7 | spec: 8 | # deployment reference 9 | targetRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: jx-production-croc-hunter-jenkinsx 13 | # HPA reference (optional) 14 | # autoscalerRef: 15 | # apiVersion: autoscaling/v2beta1 16 | # kind: HorizontalPodAutoscaler 17 | # name: jx-production-croc-hunter-jenkinsx 18 | # the maximum time in seconds for the canary deployment 19 | # to make progress before it is rollback (default 600s) 20 | progressDeadlineSeconds: 60 21 | service: 22 | # container port 23 | port: 8080 24 | # Istio gateways (optional) 25 | gateways: 26 | - public-gateway.istio-system.svc.cluster.local 27 | # Istio virtual service host names (optional) 28 | hosts: 29 | - croc-hunter.istio.us.g.csanchez.org 30 | - croc-hunter.istio.eu.g.csanchez.org 31 | canaryAnalysis: 32 | # schedule interval (default 60s) 33 | interval: 30s 34 | # max number of failed metric checks before rollback 35 | threshold: 5 36 | # max traffic percentage routed to canary 37 | # percentage (0-100) 38 | maxWeight: 50 39 | # canary increment step 40 | # percentage (0-100) 41 | stepWeight: 10 42 | metrics: 43 | - name: istio_requests_total 44 | # minimum req success rate (non 5xx responses) 45 | # percentage (0-100) 46 | threshold: 99 47 | interval: 60s 48 | - name: istio_request_duration_seconds_bucket 49 | # maximum req duration P99 50 | # milliseconds 51 | threshold: 500 52 | interval: 60s -------------------------------------------------------------------------------- /istio/README.md: -------------------------------------------------------------------------------- 1 | # Croc Hunter Canary deployment with Istio 2 | 3 | Install [Istio](https://istio.io/docs/setup/kubernetes/quick-start/) 4 | 5 | ## Access from the Internet using Istio ingress gateway 6 | 7 | Istio will route the traffic entering through the ingress gateway. 8 | Find the [ingress gateway ip address](https://istio.io/docs/tasks/traffic-management/ingress/#determining-the-ingress-ip-and-ports) and configure a wildcard DNS for it. 9 | 10 | For example map `*.example.com` to 11 | 12 | kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 13 | 14 | Create the `Gateway` that will route all external traffic through the ingress gateway 15 | 16 | kubectl create -f gateway.yaml 17 | 18 | Create the `VirtualService` that will route to the staging and production deployments (change the host to your DNS) 19 | 20 | kubectl create -f virtualservice.yaml 21 | 22 | ## Access from other services in the cluster through Istio 23 | 24 | If you need to access the service through Istio from inside the cluster (not needed for the demo) 25 | 26 | Enable Istio in the jx-staging and jx-production namespaces 27 | 28 | kubectl label namespace jx-staging istio-injection=enabled 29 | kubectl label namespace jx-production istio-injection=enabled 30 | 31 | Optional: Create a `ServiceEntry` to allow traffic to the Google metadata api to display the region 32 | 33 | kubectl create -f google-api.yaml 34 | -------------------------------------------------------------------------------- /istio/gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: Gateway 3 | metadata: 4 | name: jx-gateway 5 | namespace: istio-system 6 | spec: 7 | selector: 8 | istio: ingressgateway 9 | servers: 10 | - port: 11 | number: 80 12 | name: http 13 | protocol: HTTP 14 | hosts: 15 | - "*" 16 | # tls: 17 | # httpsRedirect: true 18 | # - port: 19 | # number: 443 20 | # name: https 21 | # protocol: HTTPS 22 | # hosts: 23 | # - "*" 24 | # tls: 25 | # mode: SIMPLE 26 | # privateKey: /etc/istio/ingressgateway-certs/tls.key 27 | # serverCertificate: /etc/istio/ingressgateway-certs/tls.crt 28 | -------------------------------------------------------------------------------- /istio/google-api.yaml: -------------------------------------------------------------------------------- 1 | # Allow calls to http://metadata.google.internal 2 | apiVersion: networking.istio.io/v1alpha3 3 | kind: ServiceEntry 4 | metadata: 5 | name: external-google-api 6 | namespace: jx-production 7 | spec: 8 | hosts: 9 | - metadata.google.internal 10 | location: MESH_EXTERNAL 11 | ports: 12 | - number: 80 13 | name: http 14 | protocol: HTTP 15 | -------------------------------------------------------------------------------- /istio/virtualservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: VirtualService 3 | metadata: 4 | name: croc-hunter-jenkinsx 5 | namespace: jx-production 6 | spec: 7 | gateways: 8 | - public-gateway.istio-system.svc.cluster.local 9 | - mesh 10 | hosts: 11 | - croc-hunter.istio.us.g.csanchez.org 12 | http: 13 | - route: 14 | - destination: 15 | host: croc-hunter-jenkinsx.jx-production.svc.cluster.local 16 | port: 17 | number: 80 18 | weight: 50 19 | - destination: 20 | host: croc-hunter-jenkinsx.jx-staging.svc.cluster.local 21 | port: 22 | number: 80 23 | weight: 50 24 | # - destination: 25 | # host: croc-hunter-jenkinsx.jx-carlossg-croc-hunter-jenkinsx-serverless-pr-35.svc.cluster.local 26 | # port: 27 | # number: 80 28 | # weight: 50 29 | -------------------------------------------------------------------------------- /shipper/README.md: -------------------------------------------------------------------------------- 1 | # Croc Hunter deployment with Shipper 2 | 3 | Install [Shipper](https://docs.shipper-k8s.io/en/latest/start/install.html) 4 | 5 | Create the cluster configuration 6 | 7 | ``` 8 | managementClusters: 9 | - name: gke-CLUSTER_NAME_US 10 | context: gke_PROJECT_ID_us-central1-a_CLUSTER_NAME_US 11 | applicationClusters: 12 | - name: gke-CLUSTER_NAME_US 13 | region: us 14 | context: gke_PROJECT_ID_us-central1-a_CLUSTER_NAME_US 15 | - name: gke-CLUSTER_NAME_EU 16 | region: europe 17 | context: gke_PROJECT_ID_europe-west4-a_CLUSTER_NAME_EU 18 | - name: gke-CLUSTER_NAME_STAGING 19 | region: staging 20 | context: gke_PROJECT_ID_us-central1-a_CLUSTER_NAME_STAGING 21 | ``` 22 | 23 | Apply the cluster configuration 24 | 25 | shipperctl admin clusters apply -f clusters.yaml 26 | 27 | Deploy croc-hunter to staging cluster 28 | 29 | kubectl apply -f croc-hunter-staging.yaml 30 | 31 | Deploy croc-hunter to production clusters 32 | 33 | kubectl apply -f croc-hunter-staging.yaml 34 | 35 | Edit the created release object to roll out 36 | 37 | kubectl get release.shipper.booking.com 38 | kubectl edit release.shipper.booking.com croc-hunter-d534b276-0 39 | 40 | Go to the next rollout step (staging, 50/50, full on), editing `spec.targetStep` to (0,1,2) 41 | -------------------------------------------------------------------------------- /shipper/croc-hunter-ingress-global.yaml: -------------------------------------------------------------------------------- 1 | # Multi cluster ingress 2 | # Access croc-hunter service deployed in US and EU clusters 3 | # https://cloud.google.com/kubernetes-engine/docs/how-to/multi-cluster-ingress 4 | 5 | # KUBECONFIG=~/.kube/config-mci gcloud container clusters get-credentials --zone=us-central1-a US_CLUSTER 6 | # KUBECONFIG=~/.kube/config-mci gcloud container clusters get-credentials --zone=europe-west4-a EU_CLUSTER 7 | # gcloud compute addresses create --global croc-hunter-mci-ip 8 | # kubemci create ingress-croc-hunter-mci --ingress=croc-hunter-ingress-global.yaml --gcp-project=PROJECT_ID --kubeconfig=~/.kube/config-mci 9 | 10 | 11 | apiVersion: extensions/v1beta1 12 | kind: Ingress 13 | metadata: 14 | namespace: test 15 | name: ingress-croc-hunter-mci 16 | annotations: 17 | kubernetes.io/ingress.global-static-ip-name: croc-hunter-mci-ip 18 | kubernetes.io/ingress.class: gce-multi-cluster 19 | spec: 20 | backend: 21 | serviceName: croc-hunter-jenkinsx 22 | servicePort: 80 23 | -------------------------------------------------------------------------------- /shipper/croc-hunter.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: shipper.booking.com/v1alpha1 2 | kind: Application 3 | metadata: 4 | namespace: test 5 | name: croc-hunter 6 | spec: 7 | revisionHistoryLimit: 3 8 | template: 9 | chart: 10 | name: croc-hunter-jenkinsx 11 | repoUrl: https://chartmuseum.jx.us.g.csanchez.org/charts 12 | version: 0.0.81 13 | clusterRequirements: 14 | regions: 15 | - name: us 16 | - name: europe 17 | strategy: 18 | steps: 19 | - name: staging 20 | capacity: 21 | contender: 1 22 | incumbent: 100 23 | traffic: 24 | contender: 0 25 | incumbent: 100 26 | - name: 50/50 27 | capacity: 28 | contender: 50 29 | incumbent: 50 30 | traffic: 31 | contender: 50 32 | incumbent: 50 33 | - name: full on 34 | capacity: 35 | contender: 100 36 | incumbent: 0 37 | traffic: 38 | contender: 100 39 | incumbent: 0 40 | values: 41 | replicaCount: 3 42 | appLabel: croc-hunter 43 | image: 44 | repository: docker-registry.jx.us.g.csanchez.org/carlossg/croc-hunter-jenkinsx 45 | # for GKE multicluster ingress 46 | service: 47 | type: NodePort 48 | nodePort: 30099 49 | 50 | -------------------------------------------------------------------------------- /skaffold.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: skaffold/v1beta1 2 | kind: Config 3 | build: 4 | artifacts: 5 | - image: changeme 6 | context: . 7 | docker: 8 | buildArgs: 9 | GIT_SHA: '{{.GIT_COMMIT}}' 10 | WORKFLOW_RELEASE: '{{.VERSION}}' 11 | tagPolicy: 12 | envTemplate: 13 | template: '{{.DOCKER_REGISTRY}}/carlossg/croc-hunter-jenkinsx:{{.VERSION}}' 14 | local: {} 15 | deploy: 16 | kubectl: {} 17 | profiles: 18 | - name: dev 19 | build: 20 | artifacts: 21 | - docker: {} 22 | tagPolicy: 23 | envTemplate: 24 | template: '{{.DOCKER_REGISTRY}}/carlossg/croc-hunter-jenkinsx:{{.DIGEST_HEX}}' 25 | local: {} 26 | deploy: 27 | helm: 28 | releases: 29 | - name: croc-hunter-jenkinsx 30 | chartPath: charts/croc-hunter-jenkinsx 31 | setValueTemplates: 32 | image.repository: '{{.DOCKER_REGISTRY}}/carlossg/croc-hunter-jenkinsx' 33 | image.tag: '{{.TAG}}' 34 | -------------------------------------------------------------------------------- /static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlossg/croc-hunter-jenkinsx-serverless/6ea6a89f133790b3db7131bf9ef3fe481684c345/static/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlossg/croc-hunter-jenkinsx-serverless/6ea6a89f133790b3db7131bf9ef3fe481684c345/static/favicon-32x32.png -------------------------------------------------------------------------------- /static/game.css: -------------------------------------------------------------------------------- 1 | body{background:#222;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1em}#canvasBg{display:block;background:#fff;margin:100px auto 0}#canvasEnemy,#canvasHud,#canvasJet{display:block;margin:-500px auto 0}.section{margin-top:20px}.name{border-radius:4px;padding:6px 10px;color:#fff}.name.server{background:#29abe2}.name.visitor{background:#36D446}.title{text-transform:uppercase;color:#aaa}.details{text-align:center;color:#fff} -------------------------------------------------------------------------------- /static/game.js: -------------------------------------------------------------------------------- 1 | var canvasBg=document.getElementById("canvasBg");var contextBg=canvasBg.getContext("2d");var canvasJet=document.getElementById("canvasJet");var contextJet=canvasJet.getContext("2d");var canvasEnemy=document.getElementById("canvasEnemy");var contextEnemy=canvasEnemy.getContext("2d");var canvasHud=document.getElementById("canvasHud");var contextHud=canvasHud.getContext("2d");contextHud.fillStyle="hsla(0, 0%, 0%, 0.5)";contextHud.font="bold 20px Arial";var jet1=new Jet;var btnPlay=new Button(265,535,220,335);var gameWidth=canvasBg.width;var gameHeight=canvasBg.height;var mouseX=0;var mouseY=0;var isPlaying=false;var requestAnimFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(callback){window.setTimeout(callback,1e3/60)};var enemies=[];var imgSprite=new Image;imgSprite.src="/static/sprite.png";imgSprite.addEventListener("load",init,false);var bgDrawX1=0;var bgDrawX2=1600;function moveBg(){bgDrawX1-=5;bgDrawX2-=5;if(bgDrawX1<=-1600)bgDrawX1=1600;if(bgDrawX2<=-1600)bgDrawX2=1600;drawBg()}function init(){spawnEnemy(5);drawMenu();document.addEventListener("click",mouseClicked,false)}function playGame(){drawBg();startLoop();updateHud();document.addEventListener("keydown",checkKeyDown,false);document.addEventListener("keyup",checkKeyUp,false)}function spawnEnemy(numSpawns){for(var i=0;i0){this.drawY-=this.speed}if(this.isRightKey&&this.rightX0){this.drawX-=this.speed}};Jet.prototype.drawAllBullets=function(){for(var i=0;i=0)this.bullets[i].draw();if(this.bullets[i].explosion.hasHit)this.bullets[i].explosion.draw()}};Jet.prototype.checkShooting=function(){if(this.isSpaceBar&&!this.isShooting){this.isShooting=true;this.bullets[this.currentBullet].fire(this.noseX,this.noseY);this.currentBullet++;if(this.currentBullet>=this.bullets.length)this.currentBullet=0}else if(!this.isSpaceBar){this.isShooting=false}};Jet.prototype.updateScore=function(points){this.score+=points;updateHud()};function clearContextJet(){contextJet.clearRect(0,0,gameWidth,gameHeight)}function Bullet(j){this.srcX=100;this.srcY=500;this.drawX=-20;this.drawY=0;this.width=18;this.height=4;this.speed=3;this.explosion=new Explosion;this.jet=j}Bullet.prototype.draw=function(){this.drawX+=this.speed;contextJet.drawImage(imgSprite,this.srcX,this.srcY,this.width,this.height,this.drawX,this.drawY,this.width,this.height);this.checkHitEnemy();if(this.drawX>gameWidth)this.recycle()};Bullet.prototype.recycle=function(){this.drawX=-20};Bullet.prototype.fire=function(noseX,noseY){this.drawX=noseX;this.drawY=noseY};Bullet.prototype.checkHitEnemy=function(){for(var i=0;i=enemies[i].drawX&&this.drawX<=enemies[i].drawX+enemies[i].width&&this.drawY>=enemies[i].drawY&&this.drawY<=enemies[i].drawY+enemies[i].height){this.explosion.drawX=enemies[i].drawX-this.explosion.width/2;this.explosion.drawY=enemies[i].drawY;this.explosion.hasHit=true;this.recycle();enemies[i].recycleEnemy();this.jet.updateScore(enemies[i].rewardPoints)}}};function Explosion(){this.srcX=742;this.srcY=495;this.drawX=0;this.drawY=0;this.width=60;this.height=55;this.currentFrame=0;this.totalFrames=10;this.hasHit=false}Explosion.prototype.draw=function(){if(this.currentFrame<=this.totalFrames){contextJet.drawImage(imgSprite,this.srcX,this.srcY,this.width,this.height,this.drawX,this.drawY,this.width,this.height);this.currentFrame++}else{this.hasHit=false;this.currentFrame=0}};function Enemy(){this.srcX=0;this.srcY=644;this.width=97;this.height=51;this.speed=2;this.drawX=Math.floor(Math.random()*1e3)+gameWidth;this.drawY=Math.floor(Math.random()*gameHeight)-this.height;this.rewardPoints=5}Enemy.prototype.draw=function(){this.drawX-=this.speed;contextEnemy.drawImage(imgSprite,this.srcX,this.srcY,this.width,this.height,this.drawX,this.drawY,this.width,this.height);this.checkEscaped()};Enemy.prototype.checkEscaped=function(){if(this.drawX+this.width<=0){this.recycleEnemy()}};Enemy.prototype.recycleEnemy=function(){this.drawX=Math.floor(Math.random()*1e3)+gameWidth;this.drawY=Math.floor(Math.random()*gameHeight)};function clearContextEnemy(){contextEnemy.clearRect(0,0,gameWidth,gameHeight)}function Button(xL,xR,yT,yB){this.xLeft=xL;this.xRight=xR;this.yTop=yT;this.yBottom=yB}Button.prototype.checkClicked=function(){return this.xLeft<=mouseX&&mouseX<=this.xRight&&this.yTop<=mouseY&&mouseY<=this.yBottom};function mouseClicked(e){mouseX=e.pageX-canvasBg.offsetLeft;mouseY=e.pageY-canvasBg.offsetTop;if(!isPlaying)if(btnPlay.checkClicked())playGame()}function checkKeyDown(e){var keyId=e.keyCode||e.which;if(keyId==38||keyId==87){jet1.isUpKey=true;e.preventDefault()}if(keyId==39||keyId==68){jet1.isRightKey=true;e.preventDefault()}if(keyId==40||keyId==83){jet1.isDownKey=true;e.preventDefault()}if(keyId==37||keyId==65){jet1.isLeftKey=true;e.preventDefault()}if(keyId==32){jet1.isSpaceBar=true;e.preventDefault()}}function checkKeyUp(e){var keyId=e.keyCode||e.which;if(keyId==38||keyId==87){jet1.isUpKey=false;e.preventDefault()}if(keyId==39||keyId==68){jet1.isRightKey=false;e.preventDefault()}if(keyId==40||keyId==83){jet1.isDownKey=false;e.preventDefault()}if(keyId==37||keyId==65){jet1.isLeftKey=false;e.preventDefault()}if(keyId==32){jet1.isSpaceBar=false;e.preventDefault()}} 2 | -------------------------------------------------------------------------------- /static/game2.js: -------------------------------------------------------------------------------- 1 | var canvasBg=document.getElementById("canvasBg");var contextBg=canvasBg.getContext("2d");var canvasJet=document.getElementById("canvasJet");var contextJet=canvasJet.getContext("2d");var canvasEnemy=document.getElementById("canvasEnemy");var contextEnemy=canvasEnemy.getContext("2d");var canvasHud=document.getElementById("canvasHud");var contextHud=canvasHud.getContext("2d");contextHud.fillStyle="hsla(0, 0%, 0%, 0.5)";contextHud.font="bold 20px Arial";var jet1=new Jet;var btnPlay=new Button(265,535,220,335);var gameWidth=canvasBg.width;var gameHeight=canvasBg.height;var mouseX=0;var mouseY=0;var isPlaying=false;var requestAnimFrame=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(callback){window.setTimeout(callback,1e3/60)};var enemies=[];var imgSprite=new Image;imgSprite.src="/static/sprite2.png";imgSprite.addEventListener("load",init,false);var bgDrawX1=0;var bgDrawX2=1600;function moveBg(){bgDrawX1-=5;bgDrawX2-=5;if(bgDrawX1<=-1600)bgDrawX1=1600;if(bgDrawX2<=-1600)bgDrawX2=1600;drawBg()}function init(){spawnEnemy(5);drawMenu();document.addEventListener("click",mouseClicked,false)}function playGame(){drawBg();startLoop();updateHud();document.addEventListener("keydown",checkKeyDown,false);document.addEventListener("keyup",checkKeyUp,false)}function spawnEnemy(numSpawns){for(var i=0;i0){this.drawY-=this.speed}if(this.isRightKey&&this.rightX0){this.drawX-=this.speed}};Jet.prototype.drawAllBullets=function(){for(var i=0;i=0)this.bullets[i].draw();if(this.bullets[i].explosion.hasHit)this.bullets[i].explosion.draw()}};Jet.prototype.checkShooting=function(){if(this.isSpaceBar&&!this.isShooting){this.isShooting=true;this.bullets[this.currentBullet].fire(this.noseX,this.noseY);this.currentBullet++;if(this.currentBullet>=this.bullets.length)this.currentBullet=0}else if(!this.isSpaceBar){this.isShooting=false}};Jet.prototype.updateScore=function(points){this.score+=points;updateHud()};function clearContextJet(){contextJet.clearRect(0,0,gameWidth,gameHeight)}function Bullet(j){this.srcX=176;this.srcY=501;this.drawX=-20;this.drawY=0;this.width=48;this.height=20;this.speed=3;this.explosion=new Explosion;this.jet=j}Bullet.prototype.draw=function(){this.drawX+=this.speed;contextJet.drawImage(imgSprite,this.srcX,this.srcY,this.width,this.height,this.drawX,this.drawY,this.width,this.height);this.checkHitEnemy();if(this.drawX>gameWidth)this.recycle()};Bullet.prototype.recycle=function(){this.drawX=-20};Bullet.prototype.fire=function(noseX,noseY){this.drawX=noseX;this.drawY=noseY};Bullet.prototype.checkHitEnemy=function(){for(var i=0;i=enemies[i].drawX&&this.drawX<=enemies[i].drawX+enemies[i].width&&this.drawY>=enemies[i].drawY&&this.drawY<=enemies[i].drawY+enemies[i].height){this.explosion.drawX=enemies[i].drawX-this.explosion.width/2;this.explosion.drawY=enemies[i].drawY;this.explosion.hasHit=true;this.recycle();enemies[i].recycleEnemy();this.jet.updateScore(enemies[i].rewardPoints)}}};function Explosion(){this.srcX=720;this.srcY=510;this.drawX=0;this.drawY=0;this.width=129;this.height=61;this.currentFrame=0;this.totalFrames=10;this.hasHit=false}Explosion.prototype.draw=function(){if(this.currentFrame<=this.totalFrames){contextJet.drawImage(imgSprite,this.srcX,this.srcY,this.width,this.height,this.drawX,this.drawY,this.width,this.height);this.currentFrame++}else{this.hasHit=false;this.currentFrame=0}};function Enemy(){this.srcX=0;this.srcY=644;this.width=97;this.height=51;this.speed=2;this.drawX=Math.floor(Math.random()*1e3)+gameWidth;this.drawY=Math.floor(Math.random()*gameHeight)-this.height;this.rewardPoints=5}Enemy.prototype.draw=function(){this.drawX-=this.speed;contextEnemy.drawImage(imgSprite,this.srcX,this.srcY,this.width,this.height,this.drawX,this.drawY,this.width,this.height);this.checkEscaped()};Enemy.prototype.checkEscaped=function(){if(this.drawX+this.width<=0){this.recycleEnemy()}};Enemy.prototype.recycleEnemy=function(){this.drawX=Math.floor(Math.random()*1e3)+gameWidth;this.drawY=Math.floor(Math.random()*gameHeight)};function clearContextEnemy(){contextEnemy.clearRect(0,0,gameWidth,gameHeight)}function Button(xL,xR,yT,yB){this.xLeft=xL;this.xRight=xR;this.yTop=yT;this.yBottom=yB}Button.prototype.checkClicked=function(){return this.xLeft<=mouseX&&mouseX<=this.xRight&&this.yTop<=mouseY&&mouseY<=this.yBottom};function mouseClicked(e){mouseX=e.pageX-canvasBg.offsetLeft;mouseY=e.pageY-canvasBg.offsetTop;if(!isPlaying)if(btnPlay.checkClicked())playGame()}function checkKeyDown(e){var keyId=e.keyCode||e.which;if(keyId==38||keyId==87){jet1.isUpKey=true;e.preventDefault()}if(keyId==39||keyId==68){jet1.isRightKey=true;e.preventDefault()}if(keyId==40||keyId==83){jet1.isDownKey=true;e.preventDefault()}if(keyId==37||keyId==65){jet1.isLeftKey=true;e.preventDefault()}if(keyId==32){jet1.isSpaceBar=true;e.preventDefault()}}function checkKeyUp(e){var keyId=e.keyCode||e.which;if(keyId==38||keyId==87){jet1.isUpKey=false;e.preventDefault()}if(keyId==39||keyId==68){jet1.isRightKey=false;e.preventDefault()}if(keyId==40||keyId==83){jet1.isDownKey=false;e.preventDefault()}if(keyId==37||keyId==65){jet1.isLeftKey=false;e.preventDefault()}if(keyId==32){jet1.isSpaceBar=false;e.preventDefault()}} 2 | -------------------------------------------------------------------------------- /static/sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlossg/croc-hunter-jenkinsx-serverless/6ea6a89f133790b3db7131bf9ef3fe481684c345/static/sprite.png -------------------------------------------------------------------------------- /static/sprite2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlossg/croc-hunter-jenkinsx-serverless/6ea6a89f133790b3db7131bf9ef3fe481684c345/static/sprite2.png -------------------------------------------------------------------------------- /watch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | make skaffold-run 4 | reflex -r "\.go$" -R "vendor.*" make skaffold-run 5 | --------------------------------------------------------------------------------