Commit 8a5c5110 authored by Sam Galson's avatar Sam Galson
Browse files

Merge branch 'pipelines' into 'master'

Pipelines

See merge request !1
parents 4442b40d d1e12bbb
Pipeline #3556 passed with stages
in 11 minutes and 43 seconds
**/_build
**/node_modules
**/_build
\ No newline at end of file
......@@ -2,17 +2,20 @@ variables:
DOCKER_DRIVER: overlay
IMAGE_NAME: xpub
BASE_DOMAIN: gateway.xpub.semioticsquares.com
CONFIGURATION_REPOSITORY: https://gitlab.coko.foundation/pubsweet/infra.git
stages:
- build
- deploy_staging
- review
- staging
- production
- demo
build:
image: docker:latest
services:
- docker:dind
stage: build
only: [master]
script:
- docker version
- docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
......@@ -21,13 +24,156 @@ build:
- docker tag $DOCKERHUB_USERNAME/$IMAGE_NAME $DOCKERHUB_USERNAME/$IMAGE_NAME:$CI_COMMIT_SHA
- docker push $DOCKERHUB_USERNAME/$IMAGE_NAME:$CI_COMMIT_SHA
deploy_staging:xpub-collabra:
# -----------------------------------------------
# xpub-collabra ---------------------------------
# -----------------------------------------------
review:xpub-collabra:
image: xpub/deployer:latest
stage: review
variables:
PACKAGE_NAME: xpub-collabra
environment:
name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME
# !! kube-lego will fail if domain > 64 chars
url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
on_stop: stop_review:xpub-collabra
only:
- branches
script:
- source deploy.sh
- create_deployment
stop_review:xpub-collabra:
image: xpub/deployer:latest
stage: review
variables:
PACKAGE_NAME: xpub-collabra
GIT_STRATEGY: none
environment:
name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME
action: stop
when: manual
script:
- source deploy.sh
- delete_deployment
staging:xpub-collabra:
image: xpub/deployer:latest
stage: deploy_staging
stage: staging
variables:
PACKAGE_NAME: xpub-collabra
environment:
name: staging
url: "https://${CI_ENVIRONMENT_SLUG}-${PACKAGE_NAME}.${BASE_DOMAIN}"
name: $PACKAGE_NAME/staging
url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
only:
- master
script:
- deploy.sh
- source deploy.sh
- create_deployment
production:xpub-collabra:
image: xpub/deployer:latest
stage: production
variables:
PACKAGE_NAME: xpub-collabra
environment:
name: $PACKAGE_NAME/production
url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
when: manual
only:
- master
script:
- source deploy.sh
- create_deployment
demo:xpub-collabra:
image: xpub/deployer:latest
stage: demo
variables:
PACKAGE_NAME: xpub-collabra
environment:
name: $PACKAGE_NAME/demo
url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
when: manual
script:
- source deploy.sh
- create_deployment
# -----------------------------------------------
# xpub-ui ---------------------------------
# -----------------------------------------------
review:xpub-ui:
image: xpub/deployer:latest
stage: review
variables:
PACKAGE_NAME: xpub-ui
environment:
name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME
# !! kube-lego will fail if domain > 64 chars
url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
on_stop: stop_review:xpub-ui
only:
- branches
script:
- source deploy.sh
- create_deployment
stop_review:xpub-ui:
image: xpub/deployer:latest
stage: review
variables:
PACKAGE_NAME: xpub-ui
GIT_STRATEGY: none
when: manual
environment:
name: $PACKAGE_NAME/review/$CI_COMMIT_REF_NAME
action: stop
script:
- source deploy.sh
- delete_deployment
staging:xpub-ui:
image: xpub/deployer:latest
stage: staging
variables:
PACKAGE_NAME: xpub-ui
environment:
name: $PACKAGE_NAME/staging
url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
only:
- master
script:
- source deploy.sh
- create_deployment
production:xpub-ui:
image: xpub/deployer:latest
stage: production
variables:
PACKAGE_NAME: xpub-ui
environment:
name: $PACKAGE_NAME/production
url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
when: manual
only:
- master
script:
- source deploy.sh
- create_deployment
demo:xpub-ui:
image: xpub/deployer:latest
stage: demo
variables:
PACKAGE_NAME: xpub-ui
environment:
name: $PACKAGE_NAME/demo
url: "https://${CI_ENVIRONMENT_SLUG}.${BASE_DOMAIN}"
when: manual
script:
- source deploy.sh
- create_deployment
**/_build
**/node_modules
......@@ -22,13 +22,24 @@ COPY package.json yarn.lock lerna.json ./
COPY packages packages
RUN [ "yarn", "config", "set", "workspaces-experimental", "true" ]
# We do a development install because react-styleguidist is a dev dependency
RUN [ "yarn", "install", "--frozen-lockfile" ]
RUN [ "npm", "rebuild", "bcrypt", "--build-from-source=bcrypt"]
ENV NODE_ENV "production"
# We are temporarily going to use the same image with different commands to deploy different apps in the monorepo. This is bad :(.
WORKDIR ${HOME}/packages/xpub-collabra
# TODO pass in username and password as build arguments
RUN [ "npx", "pubsweet", "setupdb", "--", "--username=cokouser", "--password=cokopassword", "--email=cokouser@example.com" ]
RUN [ "npx", "pubsweet", "build"]
WORKDIR ${HOME}/packages/xpub-ui
RUN [ "npm", "run", "styleguide:build" ]
# Create file for kubernetes health checks
RUN touch ./styleguide/health
EXPOSE 3000
WORKDIR ${HOME}
......
......@@ -55,58 +55,9 @@ To enable manuscript conversion via INK, add the following values to `packages/x
2. The first time you run the app, initialise the database with `yarn run setupdb` (press Enter when asked for a collection title, to skip that step).
3. `yarn start`
## Deployment
## CI
CI requires a Kubernetes cluster, resources for which can be found in the `infra` folder. In order to set up a Kubernetes cluster (using AWS) you need to install `kops`, the AWS CLI and `kubectl`. Then follow the instructions below.
### Prepare `kops`
Before you can create a cluster you need to perform the steps described in [Setup your environment](https://github.com/kubernetes/kops/blob/master/docs/aws.md#setup-your-environment) in the `kops` tutorial, i.e.:
- set up IAM user
- configure DNS
- create s3 bucket for cluster state storage (must use region `us-east-1`)
When that is done, you need to export several environment variables:
- `AWS_SECRET_ACCESS_KEY`
- `AWS_ACCESS_KEY_ID`
- `AWS_HOSTED_ZONE` (the hosted zone you set up in the previous steps)
- `GITLAB_ORG` (the organisation of the project where you wish to set up Kubernetes integration)
- `GITLAB_PROJECT` (the project where you wish to set up Kubernetes integration)
- `GITLAB_KUBE_NAMESPACE` (the namespace Kubernetes integration will use
- `GITLAB_ACCESS_TOKEN` (personal access token for GitLab API)
- `LETS_ENCRYPT_EMAIL` (email to be used by kube-lego for Let's Encrypt)
### Create a cluster
`infra/cluster.sh` is a script to simplify deployment of a Kubernetes cluster on AWS using `kops`. Run the following command from the project's root directory to create a cluster:
```bash
./infra/cluster.sh create
```
The script creates a Kubernetes cluster with:
- one master (m3.medium)
- two nodes (t2.medium)
- a load balancer
The following add-ons are also installed:
- ingress-nginx
- kubernetes-dashboard
- heapster
GitLab integration is also set up automatically for the repository specified by `GITLAB_ORG` and `GITLAB_PROJECT`. See [GitLab's docs](https://docs.gitlab.com/ce/user/project/integrations/kubernetes.html#configuration) for more info.
Secrets needed for dashboard access and GitLab integration are reported on stdout.
### Destroy the cluster
Run
```bash
./infra/cluster.sh destroy
```
to bring down the cluster.
CI requires a Kubernetes cluster, resources for which can be found in `pubsweet/infra`. In order to set up a Kubernetes cluster (using AWS) you need to follow the instructions there.
## Community
......
{
"couchdb": {
"uuid": "fc06236e-0908-4a5c-b08e-560ff042e338"
}
}
#!/bin/bash
export KOPS_STATE_STORE="s3://$(echo $AWS_HOSTED_ZONE | sed 's/\./-/g')-state-store"
export CLUSTER_NAME="cluster.${AWS_HOSTED_ZONE}"
export GATEWAY_DOMAIN="gateway.${AWS_HOSTED_ZONE}"
command -v kops >/dev/null 2>&1 || { echo >&2 "Please install kops before proceeding. Aborting."; exit 1; }
command -v kubectl >/dev/null 2>&1 || { echo >&2 "Please install kubectl before proceeding. Aborting."; exit 1; }
command -v aws >/dev/null 2>&1 || { echo >&2 "Please install AWS CLI before proceeding. Aborting."; exit 1; }
if [ -z "$AWS_HOSTED_ZONE" ]; then
echo >&2 "Please set AWS_HOSTED_ZONE. Aborting."
exit 1
fi
if [ -z "$GITLAB_ORG" ]; then
echo >&2 "Please set GITLAB_ORG to the name of the organisation the repository to integrate belongs to. Aborting."
exit 1
fi
if [ -z "$GITLAB_PROJECT" ]; then
echo >&2 "Please set GITLAB_PROJECT to the name of the repository to integrate. Aborting."
exit 1
fi
if [ -z "$GITLAB_KUBE_NAMESPACE" ]; then
echo >&2 "Please set GITLAB_KUBE_NAMESPACE to the namespace you want Kubernetes integration to use. Aborting."
exit 1
fi
if [ -z "$GITLAB_ACCESS_TOKEN" ]; then
echo >&2 "Please set GITLAB_ACCESS_TOKEN to your personal access token. Aborting."
exit 1
fi
if [ -z "$LETS_ENCRYPT_EMAIL" ]; then
echo >&2 "Please set LETS_ENCRYPT_EMAIL to your email. Aborting."
exit 1
fi
if [ ! -f lerna.json ]; then
echo >&2 "Please run this script from the project's root directory. Aborting."
exit 1
fi
NC='\033[0m'
RED='\033[0;31m'
GREEN='\033[0;32m'
create_lb_cname_record() {
HOSTED_ZONE_ID=$(aws route53 list-hosted-zones --query "HostedZones[?Name==\`$AWS_HOSTED_ZONE.\`].Id | [0]" --output text)
CNAME_RECORD=$(cat << EOF
{
"Comment": "Point stable gateway domain to kubernetes load balancer",
"Changes": [
{
"Action": "UPSERT",
"ResourceRecordSet": {
"Name": "${GATEWAY_DOMAIN}.",
"Type": "CNAME",
"TTL": 300,
"ResourceRecords": [
{
"Value": "${KUBERNETES_LB_HOSTNAME}"
}
]
}
}
]
}
EOF
)
CHANGE_REQ_ID=$(aws route53 change-resource-record-sets --hosted-zone-id "$HOSTED_ZONE_ID" --change-batch "$CNAME_RECORD" --query "ChangeInfo.Id" --output text)
echo -e "${GREEN}>>> Waiting for CNAME record change with ID $CHANGE_REQ_ID to propagate (~1 min)...\n${NC}"
until [ $(aws route53 get-change --id "$CHANGE_REQ_ID" --query "ChangeInfo.Status" --output text) == 'INSYNC' ]; do
sleep 3
done
echo -e "${GREEN}>>> CNAME record creation complete! Gateway domain is $GATEWAY_DOMAIN\n${NC}"
}
create_cluster() {
echo -e "\n${GREEN}>>> Preparing cluster manifest..."
echo -e ">>> Cluster name: $CLUSTER_NAME"
echo -e ">>> State store: $KOPS_STATE_STORE\n${NC}"
kops create cluster \
--zones us-east-1a \
--name ${CLUSTER_NAME}
# some additional optional options
# --cloud=aws (required for certain versions of kops)
# --node-count 2 \
# --master-size=m3.medium \
# --node-size=t2.medium \
# --node-volume-size=20 \
echo -e "${GREEN}>>> Creating cluster...\n${NC}"
kops update cluster ${CLUSTER_NAME} --yes
echo -e "${GREEN}>>> Waiting for cluster to come up. Could take several minutes...\n${NC}"
until kops validate cluster >/dev/null 2>&1; do
sleep 10
done
kops validate cluster
echo -e "\n${GREEN}>>> Cluster ready!\n${NC}"
kubectl version
echo -e "\n${GREEN}>>> Installing dashboard\n${NC}"
kubectl create -f https://raw.githubusercontent.com/kubernetes/kops/master/addons/kubernetes-dashboard/v1.7.1.yaml
echo -e "\n${GREEN}>>> Installing heapster\n${NC}"
kubectl create -f https://raw.githubusercontent.com/kubernetes/kops/master/addons/monitoring-standalone/v1.7.0.yaml
KUBERNETES_TOKEN=$(kops get secrets kube --type secret -oplaintext)
echo -e "\n${GREEN}>>> Access dashboard at https://api.${CLUSTER_NAME}/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard"
echo -e ">>> Dashboard username: admin"
echo -e ">>> Dashboard password: $KUBERNETES_TOKEN\n${NC}"
echo -e "${GREEN}>>> Installing ingress-nginx\n${NC}"
kubectl apply -f https://raw.githubusercontent.com/kubernetes/kops/master/addons/ingress-nginx/v1.6.0.yaml
echo -e "\n${GREEN}>>> Waiting for load balancer to come up...\n${NC}"
get_lb_hostname() {
kubectl -n kube-ingress get svc -owide | awk 'FNR == 2 {print $4}'
}
while [ $(get_lb_hostname) == '<pending>' ]; do
sleep 3
done
KUBERNETES_LB_HOSTNAME=$(get_lb_hostname)
echo -e "${GREEN}>>> Load balancer hostname: $KUBERNETES_LB_HOSTNAME\n${NC}"
echo -e "${GREEN}>>> Creating CNAME record for hostname...\n${NC}"
create_lb_cname_record
echo -e "${GREEN}>>> Installing kube-lego\n${NC}"
kubectl apply -f ./infra/kube-lego-namespace.yaml
sed -e "s/\${LETS_ENCRYPT_EMAIL}/${LETS_ENCRYPT_EMAIL}/" ./infra/kube-lego-configmap.yaml | kubectl apply -f -
kubectl apply -f ./infra/kube-lego-deployment.yaml
echo -e "\n${GREEN}>>> Installing echoheaders test deployment\n${NC}"
kubectl run echoheaders --image=gcr.io/google_containers/echoserver:1.4 --replicas=1 --port=8080
kubectl expose deployment echoheaders --port=80 --target-port=8080 --name=echoheaders
kubectl apply -f ./infra/echoheaders-ingress.yaml
echo -e "\n${GREEN}>>> Creating GitLab Kubernetes Integration for $GITLAB_ORG/$GITLAB_PROJECT...\n${NC}"
KUBERNETES_SECRET=$(kubectl get secrets | awk 'FNR == 2 {print $1}')
KUBERNETES_SECRET_CERT=$(kubectl get secret ${KUBERNETES_SECRET} -o jsonpath='{.data.ca\.crt}' | base64 --decode)
KUBERNETES_SECRET_TOKEN=$(kubectl get secret ${KUBERNETES_SECRET} -o jsonpath='{.data.token}' | base64 --decode)
echo -e "${GREEN}>>> GitLab Intagration 'API URL' is below:${NC}\nhttps://api.${CLUSTER_NAME}\n"
echo -e "${GREEN}>>> GitLab Integration 'Custom CA bundle' is below:${NC}\n${KUBERNETES_SECRET_CERT}\n"
echo -e "${GREEN}>>> GitLab Integration 'Service token' is below:\n${NC}${KUBERNETES_SECRET_TOKEN}\n"
# We must embed literal newlines into PEM file in order to include in JSON
KUBERNETES_SECRET_CERT=$(echo "${KUBERNETES_SECRET_CERT}" | awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}')
GITLAB_KUBE_INTEGRATION=$(cat << EOF
{
"namespace": "${GITLAB_KUBE_NAMESPACE}",
"api_url": "https://api.${CLUSTER_NAME}",
"token": "${KUBERNETES_SECRET_TOKEN}",
"ca_pem": "${KUBERNETES_SECRET_CERT}"
}
EOF
)
GITLAB_RESPONSE=$(curl -X PUT \
-H "Content-Type: application/json" \
-H "Private-Token: ${GITLAB_ACCESS_TOKEN}" \
-d "${GITLAB_KUBE_INTEGRATION}" \
"https://gitlab.coko.foundation/api/v4/projects/${GITLAB_ORG}%2F${GITLAB_PROJECT}/services/kubernetes")
if [[ -n $(echo $GITLAB_RESPONSE | grep 'created_at') ]]; then
echo -e "${GREEN}>>> GitLab Kubernetes Integration SUCCESS.\n${NC}"
else
echo -e >&2 "${RED}>>> GitLab Kubernetes Integration FAILED.\n${NC}"
fi
echo -e "\n${GREEN}>>> Smoke testing dummy deployment at https://$GATEWAY_DOMAIN/echotest${NC}"
if [ $(curl -H 'this: should-be-echoed' "https://$GATEWAY_DOMAIN/echotest" | grep 'should-be-echoed') ]; then
echo -e "\n${GREEN}>>> TEST PASSED!${NC}"
else
echo -e >&2 "\n${RED}>>> TEST FAILED!${NC}"
exit 1
fi
}
destroy_cluster() {
echo -e "\n${GREEN}>>> Destroying cluster...\n${NC}"
kops delete cluster --name $CLUSTER_NAME --yes
}
if [ $1 == 'create' ]; then
create_cluster
elif [ $1 == 'destroy' ]; then
destroy_cluster
else
echo -e >&2 ">>> Invalid command \"$1\": use \"cluster.sh create\" or \"cluster.sh destroy\""
fi
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: echomap
annotations:
kubernetes.io/tls-acme: "true"
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- gateway.xpub.semioticsquares.com
secretName: echoserver-tls
rules:
- host: gateway.xpub.semioticsquares.com
http:
paths:
- path: /echotest
backend:
serviceName: echoheaders
servicePort: 80
apiVersion: v1
metadata:
name: kube-lego
namespace: kube-lego
data:
lego.email: ${LETS_ENCRYPT_EMAIL}
lego.url: "https://acme-v01.api.letsencrypt.org/directory"
kind: ConfigMap
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kube-lego
namespace: kube-lego
spec:
replicas: 1
template:
metadata:
labels:
app: kube-lego
spec:
containers:
- name: kube-lego
image: jetstack/kube-lego:0.1.5
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: LEGO_EMAIL
valueFrom:
configMapKeyRef:
name: kube-lego
key: lego.email
- name: LEGO_URL
valueFrom:
configMapKeyRef:
name: kube-lego
key: lego.url
- name: LEGO_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: LEGO_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
timeoutSeconds: 1
apiVersion: v1
kind: Namespace
metadata:
name: kube-lego
apiVersion: apps/v1beta1 #https://github.com/kubernetes/kubernetes/issues/55894
kind: Deployment
metadata:
name: xpub-collabra-${CI_ENVIRONMENT_SLUG}
namespace: ${KUBE_NAMESPACE}
labels:
app: xpub-collabra-${CI_ENVIRONMENT_SLUG}
track: stable
spec:
replicas: 1
selector:
matchLabels:
app: xpub-collabra-${CI_ENVIRONMENT_SLUG}
template:
metadata:
labels:
app: xpub-collabra-${CI_ENVIRONMENT_SLUG}
track: stable
spec:
containers:
- name: xpub-collabra-${CI_ENVIRONMENT_SLUG}
image: ${DOCKERHUB_USERNAME}/${IMAGE_NAME}:${CI_COMMIT_SHA}
workingDir: /home/xpub/packages/${PACKAGE_NAME}
command: ["npx", "pubsweet", "start"]
imagePullPolicy: Always
ports:
- name: xpub-collabra
containerPort: 3000
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 3
timeoutSeconds: 2
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 3
timeoutSeconds: 2
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: xpub-collabra-${CI_ENVIRONMENT_SLUG}