Merge pull request #81 from cmeury/integration-test
Run simple integration test against minikube (local & CI)
This commit is contained in:
commit
635046fa23
|
|
@ -0,0 +1,97 @@
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.7
|
||||||
|
working_directory: /go/src/github.com/roboll/helmfile
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: make build
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: /go/src/github.com/roboll/helmfile
|
||||||
|
paths:
|
||||||
|
- .
|
||||||
|
|
||||||
|
test:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.7
|
||||||
|
working_directory: /go/src/github.com/roboll/helmfile
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: make check
|
||||||
|
- run: make pristine
|
||||||
|
- run: make test
|
||||||
|
|
||||||
|
# thanks to https://raw.githubusercontent.com/weaveworks/launcher/master/.circleci/config.yml
|
||||||
|
integration_tests:
|
||||||
|
machine: true
|
||||||
|
environment:
|
||||||
|
CHANGE_MINIKUBE_NONE_USER: true
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: mkdir ~/build
|
||||||
|
- attach_workspace:
|
||||||
|
at: ~/build
|
||||||
|
- run: cp ~/build/helmfile ~/project/helmfile
|
||||||
|
- run:
|
||||||
|
name: Install kubectl
|
||||||
|
command: |
|
||||||
|
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.8.4/bin/linux/amd64/kubectl
|
||||||
|
chmod +x kubectl
|
||||||
|
sudo mv kubectl /usr/local/bin/
|
||||||
|
- run:
|
||||||
|
name: Install minikube
|
||||||
|
command: |
|
||||||
|
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.25.0/minikube-linux-amd64
|
||||||
|
chmod +x minikube
|
||||||
|
sudo mv minikube /usr/local/bin/
|
||||||
|
- run:
|
||||||
|
name: Install helm
|
||||||
|
command: |
|
||||||
|
HELM_VERSION=v2.8.2
|
||||||
|
HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||||
|
curl -Lo ${HELM_FILENAME} "https://kubernetes-helm.storage.googleapis.com/${HELM_FILENAME}"
|
||||||
|
tar zxf ${HELM_FILENAME} linux-amd64/helm
|
||||||
|
chmod +x linux-amd64/helm
|
||||||
|
sudo mv linux-amd64/helm /usr/local/bin/
|
||||||
|
- run:
|
||||||
|
name: Start minikube
|
||||||
|
command: |
|
||||||
|
sudo minikube start --vm-driver=none
|
||||||
|
sudo chown -R $USER.$USER ~/.minikube
|
||||||
|
sudo chown -R $USER.$USER ~/.kube
|
||||||
|
minikube update-context
|
||||||
|
- run:
|
||||||
|
name: Wait for nodes to become ready
|
||||||
|
command: JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done
|
||||||
|
- run:
|
||||||
|
name: Execute integration tests
|
||||||
|
command: |
|
||||||
|
export TERM=xterm
|
||||||
|
make integration
|
||||||
|
|
||||||
|
# GITHUB_TOKEN env var must be setup in circleci console
|
||||||
|
deployment:
|
||||||
|
release:
|
||||||
|
tag: /v.*/
|
||||||
|
commands:
|
||||||
|
- docker login -e="." -u="$DOCKER_USER" -p="$DOCKER_PASS" quay.io
|
||||||
|
- cd "$WORK" && make tools
|
||||||
|
- cd "$WORK" && BUILD_URL="$CIRCLE_BUILD_URL" make push release
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
build_and_test:
|
||||||
|
jobs:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- integration_tests:
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- /pull.*/
|
||||||
8
Makefile
8
Makefile
|
|
@ -21,10 +21,18 @@ test:
|
||||||
go test -v ${PKGS} -cover -race -p=1
|
go test -v ${PKGS} -cover -race -p=1
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
|
||||||
|
integration:
|
||||||
|
bash test/integration/run.sh
|
||||||
|
.PHONY: integration
|
||||||
|
|
||||||
cross:
|
cross:
|
||||||
gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}' ${TARGETS}
|
gox -os '!freebsd !netbsd' -arch '!arm' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X main.Version=${TAG}' ${TARGETS}
|
||||||
.PHONY: cross
|
.PHONY: cross
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm dist/helmfile_*
|
||||||
|
.PHONY: clean
|
||||||
|
|
||||||
pristine: generate fmt
|
pristine: generate fmt
|
||||||
git ls-files --exclude-standard --modified --deleted --others | diff /dev/null -
|
git ls-files --exclude-standard --modified --deleted --others | diff /dev/null -
|
||||||
.PHONY: pristine
|
.PHONY: pristine
|
||||||
|
|
|
||||||
34
circle.yml
34
circle.yml
|
|
@ -1,34 +0,0 @@
|
||||||
machine:
|
|
||||||
services: [ docker ]
|
|
||||||
post:
|
|
||||||
- mkdir -p download
|
|
||||||
- test -e download/$GODIST || curl -o download/$GODIST https://storage.googleapis.com/golang/$GODIST
|
|
||||||
- sudo rm -rf /usr/local/go
|
|
||||||
- sudo tar -C /usr/local -xzf download/$GODIST
|
|
||||||
environment:
|
|
||||||
GODIST: "go1.7.linux-amd64.tar.gz"
|
|
||||||
GOPATH: "$HOME/go"
|
|
||||||
PATH: "$PATH:$GOPATH/bin"
|
|
||||||
WORK: "$GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/"
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
override:
|
|
||||||
- mkdir -p "$WORK"
|
|
||||||
- rsync -az --delete ./ "$WORK"
|
|
||||||
|
|
||||||
test:
|
|
||||||
pre:
|
|
||||||
- cd "$WORK" && make check
|
|
||||||
- cd "$WORK" && make pristine
|
|
||||||
|
|
||||||
override:
|
|
||||||
- cd "$WORK" && make test
|
|
||||||
|
|
||||||
# GITHUB_TOKEN env var must be setup in circleci console
|
|
||||||
deployment:
|
|
||||||
release:
|
|
||||||
tag: /v.*/
|
|
||||||
commands:
|
|
||||||
- docker login -e="." -u="$DOCKER_USER" -p="$DOCKER_PASS" quay.io
|
|
||||||
- cd "$WORK" && make tools
|
|
||||||
- cd "$WORK" && BUILD_URL="$CIRCLE_BUILD_URL" make push release
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
apiVersion: v1
|
||||||
|
description: HTTP Request & Response Service, written in Python + Flask. https://httpbin.org
|
||||||
|
name: httpbin
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{{/* vim: set filetype=mustache: */}}
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "httpbin.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
*/}}
|
||||||
|
{{- define "httpbin.fullname" -}}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
metadata:
|
||||||
|
name: {{ template "httpbin.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ template "httpbin.name" . }}
|
||||||
|
chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy: {}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ template "httpbin.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: {{ .Values.service.internalPort }}
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: {{ .Values.service.internalPort }}
|
||||||
|
ports:
|
||||||
|
- containerPort: {{ .Values.service.internalPort }}
|
||||||
|
resources:
|
||||||
|
{{ toYaml .Values.resources | indent 10 }}
|
||||||
|
|
||||||
|
status: {}
|
||||||
|
---
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
{{- if .Values.ingress.enabled -}}
|
||||||
|
{{- $serviceName := include "httpbin.fullname" . -}}
|
||||||
|
{{- $servicePort := .Values.service.externalPort -}}
|
||||||
|
{{- $prefix := .Values.ingress.prefix -}}
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ template "httpbin.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ template "httpbin.name" . }}
|
||||||
|
chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
annotations:
|
||||||
|
{{- range $key, $value := .Values.ingress.annotations }}
|
||||||
|
{{ $key }}: {{ $value | quote }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
{{- range $host := .Values.ingress.hosts }}
|
||||||
|
- host: {{ $host }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
backend:
|
||||||
|
serviceName: {{ $serviceName }}
|
||||||
|
servicePort: {{ $servicePort }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if .Values.ingress.tls }}
|
||||||
|
tls:
|
||||||
|
{{ toYaml .Values.ingress.tls | indent 4 }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ template "httpbin.fullname" . }}
|
||||||
|
labels:
|
||||||
|
app: {{ template "httpbin.name" . }}
|
||||||
|
chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.externalPort }}
|
||||||
|
targetPort: {{ .Values.service.internalPort }}
|
||||||
|
protocol: TCP
|
||||||
|
name: {{ .Values.service.name }}
|
||||||
|
selector:
|
||||||
|
app: {{ template "httpbin.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
image:
|
||||||
|
repository: docker.io/citizenstig/httpbin
|
||||||
|
tag: latest
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
service:
|
||||||
|
name: httpbin
|
||||||
|
type: LoadBalancer
|
||||||
|
externalPort: 8000
|
||||||
|
internalPort: 8000
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
hosts:
|
||||||
|
- httpbin.local
|
||||||
|
resources: {}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
repositories:
|
||||||
|
- name: stable
|
||||||
|
url: https://kubernetes-charts.storage.googleapis.com/
|
||||||
|
|
||||||
|
context: minikube
|
||||||
|
|
||||||
|
releases:
|
||||||
|
|
||||||
|
- name: httpbin
|
||||||
|
chart: ./charts/httpbin
|
||||||
|
set:
|
||||||
|
- name: ingress.enabled
|
||||||
|
value: false
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Check environment is correctly setup
|
||||||
|
|
||||||
|
if ! hash minikube 2>/dev/null; then
|
||||||
|
fail "Minikube needs to be installed."
|
||||||
|
fi
|
||||||
|
if [ ! $(minikube status --format '{{.MinikubeStatus}}') == "Running" ]; then
|
||||||
|
fail "Minikube is not running."
|
||||||
|
fi
|
||||||
|
if [ ! $(minikube status --format '{{.ClusterStatus}}') == "Running" ]; then
|
||||||
|
fail "Minikube Cluster is not running."
|
||||||
|
fi
|
||||||
|
if ! kubectl version --short 1> /dev/null; then
|
||||||
|
fail "Could not connect to minikube apiserver"
|
||||||
|
fi
|
||||||
|
if ! hash curl 1>/dev/null; then
|
||||||
|
fail "curl needs to be installed."
|
||||||
|
fi
|
||||||
|
if ! hash docker 1>/dev/null; then
|
||||||
|
fail "Docker needs to be installed."
|
||||||
|
fi
|
||||||
|
if ! docker version 1> /dev/null; then
|
||||||
|
fail "Could not connect to Docker daemon"
|
||||||
|
fi
|
||||||
|
if ! hash helm 1>/dev/null; then
|
||||||
|
fail "Helm needs to be installed."
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
declare -i tests_total=0
|
||||||
|
|
||||||
|
function info () {
|
||||||
|
tput bold; tput setaf 4; echo -n "INFO: "; tput sgr0; echo "${@}"
|
||||||
|
}
|
||||||
|
function warn () {
|
||||||
|
tput bold; tput setaf 3; echo -n "WARN: "; tput sgr0; echo "${@}"
|
||||||
|
}
|
||||||
|
function fail () {
|
||||||
|
tput bold; tput setaf 1; echo -n "FAIL: "; tput sgr0; echo "${@}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
function test_start () {
|
||||||
|
tput bold; tput setaf 6; echo -n "TEST: "; tput sgr0; echo "${@}"
|
||||||
|
}
|
||||||
|
function test_pass () {
|
||||||
|
tests_total=$((tests_total+1))
|
||||||
|
tput bold; tput setaf 2; echo -n "PASS: "; tput sgr0; echo "${@}"
|
||||||
|
}
|
||||||
|
function all_tests_passed () {
|
||||||
|
tput bold; tput setaf 2; echo -n "PASS: "; tput sgr0; echo "${tests_total} tests passed"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# IMPORTS -----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# determine working directory to use to relative paths irrespective of starting directory
|
||||||
|
dir="${BASH_SOURCE%/*}"
|
||||||
|
if [[ ! -d "${dir}" ]]; then dir="${PWD}"; fi
|
||||||
|
|
||||||
|
. "${dir}/lib/output.sh"
|
||||||
|
. "${dir}/lib/ensure.sh"
|
||||||
|
|
||||||
|
|
||||||
|
# GLOBALS -----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test_ns="helmfile-tests-$(date +"%Y%m%d-%H%M%S")"
|
||||||
|
helmfile="./helmfile --namespace=${test_ns}"
|
||||||
|
helm="helm --kube-context=minikube"
|
||||||
|
kubectl="kubectl --context=minikube --namespace=${test_ns}"
|
||||||
|
|
||||||
|
# FUNCTIONS ----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function wait_deploy_ready() {
|
||||||
|
$kubectl rollout status deployment ${1}
|
||||||
|
while [ "$($kubectl get deploy ${1} -o=jsonpath='{.status.readyReplicas}')" == "0" ]; do
|
||||||
|
info "Waiting for deployment ${1} to be ready"
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# SETUP --------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
set -e
|
||||||
|
info "Using namespace: ${test_ns}"
|
||||||
|
info "Using Helm version: $(helm version --short --client | grep -o v.*$)"
|
||||||
|
$helm init --wait
|
||||||
|
$helmfile -v
|
||||||
|
$kubectl get namespace ${test_ns} &> /dev/null && warn "Namespace ${test_ns} exists, from a previous test run?"
|
||||||
|
trap "{ $kubectl delete namespace ${test_ns}; }" EXIT # remove namespace whenever we exit this script
|
||||||
|
|
||||||
|
|
||||||
|
# TEST CASES----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test_start "happypath - simple rollout of httpbin chart"
|
||||||
|
$helmfile -f ${dir}/happypath.yaml sync
|
||||||
|
wait_deploy_ready httpbin-httpbin
|
||||||
|
curl --fail $(minikube service --url --namespace=${test_ns} httpbin-httpbin)/status/200 \
|
||||||
|
|| fail "httpbin failed to return 200 OK"
|
||||||
|
$helmfile -f ${dir}/happypath.yaml delete
|
||||||
|
$helm status --namespace=${test_ns} httpbin &> /dev/null \
|
||||||
|
&& fail "release should not exist anymore after a delete"
|
||||||
|
test_pass "happypath"
|
||||||
|
|
||||||
|
|
||||||
|
# ALL DONE -----------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
all_tests_passed
|
||||||
Loading…
Reference in New Issue