initial commit from IBM Cloud example
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
commit
4e76f510b3
32 changed files with 1925 additions and 0 deletions
68
.bluemix/deploy.json
Normal file
68
.bluemix/deploy.json
Normal file
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Sample Deploy Stage",
|
||||
"longDescription": "The Delivery Pipeline automates continuous deployment.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"api-key": {
|
||||
"description": "IBM Cloud API keys contain permissions that grant access to organizations, spaces, and Kubernetes clusters. You can obtain your API key with 'ibmcloud iam api-key-create' or via the console at https://cloud.ibm.com/iam/#/apikeys by clicking **Create API key** (Each API key only can be viewed once).",
|
||||
"type": "string"
|
||||
},
|
||||
"dev-region": {
|
||||
"description": "The IBM Cloud region",
|
||||
"type": "string"
|
||||
},
|
||||
"dev-organization": {
|
||||
"description": "The IBM Cloud org",
|
||||
"type": "string"
|
||||
},
|
||||
"dev-space": {
|
||||
"description": "The IBM Cloud space",
|
||||
"type": "string"
|
||||
},
|
||||
"app-name": {
|
||||
"description": "app name",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["dev-region", "dev-organization", "dev-space", "app-name"], "form": [{
|
||||
"type": "validator",
|
||||
"url": "/devops/setup/bm-helper/helper.html"
|
||||
},
|
||||
{
|
||||
"type": "password",
|
||||
"readonly": false,
|
||||
"title": "IBM Cloud API Key",
|
||||
"key": "api-key"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"readonly": false,
|
||||
"title": "App Name",
|
||||
"key": "app-name"
|
||||
}, {
|
||||
"type": "table",
|
||||
"columnCount": 3,
|
||||
"widths": ["28%", "28%", "28%"],
|
||||
"items": [{
|
||||
"type": "label",
|
||||
"title": "Region"
|
||||
}, {
|
||||
"type": "label",
|
||||
"title": "Organization"
|
||||
}, {
|
||||
"type": "label",
|
||||
"title": "Space"
|
||||
}, {
|
||||
"type": "select",
|
||||
"key": "dev-region"
|
||||
}, {
|
||||
"type": "select",
|
||||
"key": "dev-organization"
|
||||
}, {
|
||||
"type": "select",
|
||||
"key": "dev-space",
|
||||
"readonly": false
|
||||
}]
|
||||
}]
|
||||
}
|
88
.bluemix/pipeline.yml
Normal file
88
.bluemix/pipeline.yml
Normal file
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
stages:
|
||||
- name: Build Stage
|
||||
inputs:
|
||||
- type: git
|
||||
branch: master
|
||||
service: ${REPO}
|
||||
properties:
|
||||
- name: CF_HOSTNAME
|
||||
value: "${CF_HOSTNAME}"
|
||||
type: text
|
||||
- name: CF_DOMAIN
|
||||
value: "${CF_DOMAIN}"
|
||||
type: text
|
||||
triggers:
|
||||
- type: commit
|
||||
jobs:
|
||||
- name: Build
|
||||
type: builder
|
||||
artifact_dir: ''
|
||||
build_type: shell
|
||||
script: |-
|
||||
#!/bin/bash
|
||||
if [[ -f post_build.sh ]]; then
|
||||
chmod +x post_build.sh;
|
||||
echo "executing the post_build script";
|
||||
sh post_build.sh;
|
||||
else
|
||||
echo "the post_build script does not exist";
|
||||
fi
|
||||
- name: Deploy Stage
|
||||
inputs:
|
||||
- type: job
|
||||
stage: Build Stage
|
||||
job: Build
|
||||
properties:
|
||||
- name: CF_HOSTNAME
|
||||
value: "${CF_HOSTNAME}"
|
||||
type: text
|
||||
- name: CF_DOMAIN
|
||||
value: "${CF_DOMAIN}"
|
||||
type: text
|
||||
triggers:
|
||||
- type: stage
|
||||
jobs:
|
||||
- name: Deploy
|
||||
type: deployer
|
||||
target:
|
||||
region_id: ${REGION_ID}
|
||||
organization: ${CF_ORGANIZATION}
|
||||
space: ${CF_SPACE}
|
||||
application: ${CF_APP}
|
||||
api_key: ${API_KEY}
|
||||
script: |-
|
||||
#!/bin/bash
|
||||
cf push "${CF_APP}" --hostname "${CF_HOSTNAME}" -d "${CF_DOMAIN}"
|
||||
# cf logs "${CF_APP}" --recent
|
||||
- name: Health Stage
|
||||
inputs:
|
||||
- type: job
|
||||
stage: Build Stage
|
||||
job: Build
|
||||
triggers:
|
||||
- type: stage
|
||||
permission:
|
||||
execute: TOOLCHAIN_ADMINS
|
||||
properties:
|
||||
- name: CF_HOSTNAME
|
||||
value: "${CF_HOSTNAME}"
|
||||
type: text
|
||||
- name: CF_DOMAIN
|
||||
value: "${CF_DOMAIN}"
|
||||
type: text
|
||||
jobs:
|
||||
- name: Test
|
||||
type: tester
|
||||
script: |-
|
||||
#!/bin/sh
|
||||
apk add --no-cache curl
|
||||
if [ "$(curl -is http://not-used.not-used.net/health --connect-timeout 3 --max-time 5 --retry 3 --retry-max-time 30 | head -n 1 | grep 200)" != "" ]; then
|
||||
echo "Successfully reached health endpoint"
|
||||
echo "====================================================================="
|
||||
else
|
||||
echo "Could not reach health endpoint: http://not-used.not-used.net/health"
|
||||
exit 1;
|
||||
fi;
|
||||
test_type: customimage
|
||||
docker_image: alpine
|
75
.bluemix/scripts/container_build.sh
Normal file
75
.bluemix/scripts/container_build.sh
Normal file
|
@ -0,0 +1,75 @@
|
|||
#!/bin/bash
|
||||
# set -x
|
||||
|
||||
echo "Build environment variables:"
|
||||
echo "REGISTRY_URL=${REGISTRY_URL}"
|
||||
echo "REGISTRY_NAMESPACE=${REGISTRY_NAMESPACE}"
|
||||
echo "IMAGE_NAME=${IMAGE_NAME}"
|
||||
echo "BUILD_NUMBER=${BUILD_NUMBER}"
|
||||
echo "ARCHIVE_DIR=${ARCHIVE_DIR}"
|
||||
|
||||
# also run 'env' command to find all available env variables
|
||||
# or learn more about the available environment variables at:
|
||||
# https://cloud.ibm.com/docs/services/ContinuousDelivery/pipeline_deploy_var.html#deliverypipeline_environment
|
||||
|
||||
# To review or change build options use:
|
||||
# ibmcloud cr build --help
|
||||
|
||||
echo "Checking registry namespace: ${REGISTRY_NAMESPACE}"
|
||||
NS=$( ibmcloud cr namespaces | grep ${REGISTRY_NAMESPACE} ||: )
|
||||
if [ -z "${NS}" ]; then
|
||||
echo -e "Registry namespace ${REGISTRY_NAMESPACE} not found, creating it."
|
||||
ibmcloud cr namespace-add ${REGISTRY_NAMESPACE}
|
||||
echo -e "Registry namespace ${REGISTRY_NAMESPACE} created."
|
||||
else
|
||||
echo -e "Registry namespace ${REGISTRY_NAMESPACE} found."
|
||||
fi
|
||||
|
||||
echo -e "Existing images in registry"
|
||||
ibmcloud cr images
|
||||
|
||||
echo "=========================================================="
|
||||
echo -e "BUILDING CONTAINER IMAGE: ${IMAGE_NAME}:${BUILD_NUMBER}"
|
||||
set -x
|
||||
ibmcloud cr build -t ${REGISTRY_URL}/${REGISTRY_NAMESPACE}/${IMAGE_NAME}:${BUILD_NUMBER} .
|
||||
set +x
|
||||
ibmcloud cr image-inspect ${REGISTRY_URL}/${REGISTRY_NAMESPACE}/${IMAGE_NAME}:${BUILD_NUMBER}
|
||||
|
||||
export PIPELINE_IMAGE_URL="$REGISTRY_URL/$REGISTRY_NAMESPACE/$IMAGE_NAME:$BUILD_NUMBER"
|
||||
|
||||
ibmcloud cr images
|
||||
|
||||
echo "=========================================================="
|
||||
echo "COPYING ARTIFACTS needed for deployment and testing (in particular build.properties)"
|
||||
|
||||
echo "Checking archive dir presence"
|
||||
mkdir -p $ARCHIVE_DIR
|
||||
|
||||
# Persist env variables into a properties file (build.properties) so that all pipeline stages consuming this
|
||||
# build as input and configured with an environment properties file valued 'build.properties'
|
||||
# will be able to reuse the env variables in their job shell scripts.
|
||||
|
||||
# CHART information from build.properties is used in Helm Chart deployment to set the release name
|
||||
CHART_NAME=$(find chart/. -maxdepth 2 -type d -name '[^.]?*' -printf %f -quit)
|
||||
echo "CHART_NAME=${CHART_NAME}" >> $ARCHIVE_DIR/build.properties
|
||||
# IMAGE information from build.properties is used in Helm Chart deployment to set the release name
|
||||
echo "IMAGE_NAME=${IMAGE_NAME}" >> $ARCHIVE_DIR/build.properties
|
||||
echo "BUILD_NUMBER=${BUILD_NUMBER}" >> $ARCHIVE_DIR/build.properties
|
||||
# REGISTRY information from build.properties is used in Helm Chart deployment to generate cluster secret
|
||||
echo "REGISTRY_URL=${REGISTRY_URL}" >> $ARCHIVE_DIR/build.properties
|
||||
echo "REGISTRY_NAMESPACE=${REGISTRY_NAMESPACE}" >> $ARCHIVE_DIR/build.properties
|
||||
echo "File 'build.properties' created for passing env variables to subsequent pipeline jobs:"
|
||||
cat $ARCHIVE_DIR/build.properties
|
||||
|
||||
echo "Copy pipeline scripts along with the build"
|
||||
# Copy scripts (incl. deploy scripts)
|
||||
if [ -d ./scripts/ ]; then
|
||||
if [ ! -d $ARCHIVE_DIR/scripts/ ]; then # no need to copy if working in ./ already
|
||||
cp -r ./scripts/ $ARCHIVE_DIR/
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Copy Helm chart along with the build"
|
||||
if [ ! -d $ARCHIVE_DIR/chart/ ]; then # no need to copy if working in ./ already
|
||||
cp -r ./chart/ $ARCHIVE_DIR/
|
||||
fi
|
136
.bluemix/scripts/kube_deploy.sh
Normal file
136
.bluemix/scripts/kube_deploy.sh
Normal file
|
@ -0,0 +1,136 @@
|
|||
#!/bin/bash
|
||||
#set -x
|
||||
|
||||
#View build properties
|
||||
cat build.properties
|
||||
|
||||
echo "Check cluster availability"
|
||||
IP_ADDR=$(ibmcloud cs workers ${PIPELINE_KUBERNETES_CLUSTER_NAME} | grep normal | head -n 1 | awk '{ print $2 }')
|
||||
if [ -z $IP_ADDR ]; then
|
||||
echo "$PIPELINE_KUBERNETES_CLUSTER_NAME not created or workers not ready"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Configuring cluster namespace"
|
||||
if kubectl get namespace ${CLUSTER_NAMESPACE}; then
|
||||
echo -e "Namespace ${CLUSTER_NAMESPACE} found."
|
||||
else
|
||||
kubectl create namespace ${CLUSTER_NAMESPACE}
|
||||
echo -e "Namespace ${CLUSTER_NAMESPACE} created."
|
||||
fi
|
||||
|
||||
echo "Configuring cluster role binding"
|
||||
if kubectl get clusterrolebinding kube-system:default; then
|
||||
echo -e "Cluster role binding found."
|
||||
else
|
||||
kubectl create clusterrolebinding kube-system:default --clusterrole=cluster-admin --serviceaccount=kube-system:default
|
||||
echo -e "Cluster role binding created."
|
||||
fi
|
||||
|
||||
echo "Configuring Tiller (Helm's server component)"
|
||||
helm init --upgrade
|
||||
kubectl rollout status -w deployment/tiller-deploy --namespace=kube-system
|
||||
while [ "$(helm version | grep "Tiller")" != "" ]; do
|
||||
echo "Waiting for server..."
|
||||
sleep 10
|
||||
done
|
||||
helm version
|
||||
|
||||
echo "CHART_NAME: $CHART_NAME"
|
||||
|
||||
echo "DEFINE RELEASE by prefixing image (app) name with namespace if not 'default' as Helm needs unique release names across namespaces"
|
||||
if [[ "${CLUSTER_NAMESPACE}" != "default" ]]; then
|
||||
RELEASE_NAME="${CLUSTER_NAMESPACE}-${IMAGE_NAME}"
|
||||
else
|
||||
RELEASE_NAME=${IMAGE_NAME}
|
||||
fi
|
||||
echo "RELEASE_NAME: $RELEASE_NAME"
|
||||
|
||||
echo "CHECKING CHART (lint)"
|
||||
helm lint chart/${CHART_NAME}
|
||||
|
||||
IMAGE_REPOSITORY=${REGISTRY_URL}/${REGISTRY_NAMESPACE}/${IMAGE_NAME}
|
||||
|
||||
# Using 'upgrade --install" for rolling updates. Note that subsequent updates will occur in the same namespace the release is currently deployed in, ignoring the explicit--namespace argument".
|
||||
echo -e "Dry run into: ${PIPELINE_KUBERNETES_CLUSTER_NAME}/${CLUSTER_NAMESPACE}."
|
||||
helm upgrade --install --debug --dry-run ${RELEASE_NAME} ./chart/${CHART_NAME} --namespace ${CLUSTER_NAMESPACE} --set image.repository=${IMAGE_REPOSITORY},image.tag=${BUILD_NUMBER}
|
||||
|
||||
echo -e "Deploying into: ${PIPELINE_KUBERNETES_CLUSTER_NAME}/${CLUSTER_NAMESPACE}."
|
||||
helm upgrade --install ${RELEASE_NAME} ./chart/${CHART_NAME} --namespace ${CLUSTER_NAMESPACE} --set image.repository=${IMAGE_REPOSITORY},image.tag=${BUILD_NUMBER}
|
||||
|
||||
echo -e "CHECKING deployment status of release ${RELEASE_NAME} with image tag: ${BUILD_NUMBER}"
|
||||
echo ""
|
||||
for ITERATION in {1..30}
|
||||
do
|
||||
DATA=$( kubectl get pods --namespace ${CLUSTER_NAMESPACE} -a -l release=${RELEASE_NAME} -o json )
|
||||
NOT_READY=$( echo $DATA | jq '.items[].status.containerStatuses[] | select(.image=="'"${IMAGE_REPOSITORY}:${BUILD_NUMBER}"'") | select(.ready==false) ' )
|
||||
if [[ -z "$NOT_READY" ]]; then
|
||||
echo -e "All pods are ready:"
|
||||
echo $DATA | jq '.items[].status.containerStatuses[] | select(.image=="'"${IMAGE_REPOSITORY}:${BUILD_NUMBER}"'") | select(.ready==true) '
|
||||
break # deployment succeeded
|
||||
fi
|
||||
REASON=$(echo $DATA | jq '.items[].status.containerStatuses[] | select(.image=="'"${IMAGE_REPOSITORY}:${BUILD_NUMBER}"'") | .state.waiting.reason')
|
||||
echo -e "${ITERATION} : Deployment still pending..."
|
||||
echo -e "NOT_READY:${NOT_READY}"
|
||||
echo -e "REASON: ${REASON}"
|
||||
if [[ ${REASON} == *ErrImagePull* ]] || [[ ${REASON} == *ImagePullBackOff* ]]; then
|
||||
echo "Detected ErrImagePull or ImagePullBackOff failure. "
|
||||
echo "Please check proper authenticating to from cluster to image registry (e.g. image pull secret)"
|
||||
break; # no need to wait longer, error is fatal
|
||||
elif [[ ${REASON} == *CrashLoopBackOff* ]]; then
|
||||
echo "Detected CrashLoopBackOff failure. "
|
||||
echo "Application is unable to start, check the application startup logs"
|
||||
break; # no need to wait longer, error is fatal
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [[ ! -z "$NOT_READY" ]]; then
|
||||
echo ""
|
||||
echo "=========================================================="
|
||||
echo "DEPLOYMENT FAILED"
|
||||
echo "Deployed Services:"
|
||||
kubectl describe services ${RELEASE_NAME}-${CHART_NAME} --namespace ${CLUSTER_NAMESPACE}
|
||||
echo ""
|
||||
echo "Deployed Pods:"
|
||||
kubectl describe pods --selector app=${CHART_NAME} --namespace ${CLUSTER_NAMESPACE}
|
||||
echo ""
|
||||
echo "Application Logs"
|
||||
kubectl logs --selector app=${CHART_NAME} --namespace ${CLUSTER_NAMESPACE}
|
||||
echo "=========================================================="
|
||||
PREVIOUS_RELEASE=$( helm history ${RELEASE_NAME} | grep SUPERSEDED | sort -r -n | awk '{print $1}' | head -n 1 )
|
||||
echo -e "Could rollback to previous release: ${PREVIOUS_RELEASE} using command:"
|
||||
echo -e "helm rollback ${RELEASE_NAME} ${PREVIOUS_RELEASE}"
|
||||
# helm rollback ${RELEASE_NAME} ${PREVIOUS_RELEASE}
|
||||
# echo -e "History for release:${RELEASE_NAME}"
|
||||
# helm history ${RELEASE_NAME}
|
||||
# echo "Deployed Services:"
|
||||
# kubectl describe services ${RELEASE_NAME}-${CHART_NAME} --namespace ${CLUSTER_NAMESPACE}
|
||||
# echo ""
|
||||
# echo "Deployed Pods:"
|
||||
# kubectl describe pods --selector app=${CHART_NAME} --namespace ${CLUSTER_NAMESPACE}
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================================="
|
||||
echo "DEPLOYMENT SUCCEEDED"
|
||||
echo ""
|
||||
echo -e "Status for release:${RELEASE_NAME}"
|
||||
helm status ${RELEASE_NAME}
|
||||
|
||||
echo ""
|
||||
echo -e "History for release:${RELEASE_NAME}"
|
||||
helm history ${RELEASE_NAME}
|
||||
|
||||
# echo ""
|
||||
# echo "Deployed Services:"
|
||||
# kubectl describe services ${RELEASE_NAME}-${CHART_NAME} --namespace ${CLUSTER_NAMESPACE}
|
||||
# echo ""
|
||||
# echo "Deployed Pods:"
|
||||
# kubectl describe pods --selector app=${CHART_NAME} --namespace ${CLUSTER_NAMESPACE}
|
||||
|
||||
echo "=========================================================="
|
||||
IP_ADDR=$(ibmcloud cs workers ${PIPELINE_KUBERNETES_CLUSTER_NAME} | grep normal | head -n 1 | awk '{ print $2 }')
|
||||
PORT=$(kubectl get services --namespace ${CLUSTER_NAMESPACE} | grep ${RELEASE_NAME} | sed 's/[^:]*:\([0-9]*\).*/\1/g')
|
||||
echo -e "View the application health at: http://${IP_ADDR}:${PORT}/health"
|
62
.bluemix/toolchain.yml
Normal file
62
.bluemix/toolchain.yml
Normal file
|
@ -0,0 +1,62 @@
|
|||
version: '2'
|
||||
template:
|
||||
name: Continuous Delivery Toolchain
|
||||
description: "This toolchain includes tools to develop and deploy your app. Depending on your app, when you create the toolchain, the GitHub repository will either be empty or will contain source code from your app.\n\nThis toolchain uses tools that are part of the Continuous Delivery service. If an instance of that service isn't already in your organization, when you click **Create**, it is automatically added at no cost to you. For more information and terms, see the [IBM Cloud catalog](/catalog/services/continuous-delivery/).\n\nTo get started, click **Create**."
|
||||
required:
|
||||
- repo
|
||||
- build
|
||||
|
||||
toolchain:
|
||||
name: "{{form.pipeline.parameters.toolchain-name}}"
|
||||
|
||||
services:
|
||||
# Github repos
|
||||
repo:
|
||||
service_id: hostedgit
|
||||
parameters:
|
||||
repo_url: "{{#zip_url}}{{zip_url}}{{/zip_url}}{{^zip_url}}{{repository}}{{/zip_url}}"
|
||||
repo_name: "{{toolchain.name}}"
|
||||
type: clone
|
||||
has_issues: true
|
||||
enable_traceability: true
|
||||
|
||||
# Pipelines
|
||||
build:
|
||||
service_id: pipeline
|
||||
parameters:
|
||||
services:
|
||||
- repo
|
||||
name: "{{form.pipeline.parameters.app-name}}"
|
||||
ui-pipeline: true
|
||||
configuration:
|
||||
content:
|
||||
$text: pipeline.yml
|
||||
env:
|
||||
REPO: repo
|
||||
CF_APP: "{{form.pipeline.parameters.app-name}}"
|
||||
API_KEY: "{{form.pipeline.parameters.api-key}}"
|
||||
CF_SPACE: "{{form.pipeline.parameters.dev-space}}"
|
||||
CF_ORGANIZATION: "{{form.pipeline.parameters.dev-organization}}"
|
||||
CF_HOSTNAME: "{{form.pipeline.parameters.cf-host}}"
|
||||
CF_DOMAIN: "{{form.pipeline.parameters.cf-domain}}"
|
||||
REGION_ID: "{{form.pipeline.parameters.dev-region}}"
|
||||
execute: true
|
||||
|
||||
#Web IDE
|
||||
webide:
|
||||
service_id: orion
|
||||
|
||||
#Deployment
|
||||
form:
|
||||
pipeline:
|
||||
schema:
|
||||
$ref: deploy.json
|
||||
parameters:
|
||||
app-name: "{{app-name}}"
|
||||
toolchain-name: "{{toolchain-name}}"
|
||||
dev-region: "{{region}}"
|
||||
api-key: "{{api-key}}"
|
||||
dev-space: "{{space}}"
|
||||
dev-organization: "{{organization}}"
|
||||
cf-host: "{{cf-host}}"
|
||||
cf-domain: "{{cf-domain}}"
|
Reference in a new issue