Add configuration to stop and start a GCP host BOSH Lite in the CircleCI pipeline

Template credentials required by BOSH from Circle CI environment variables
Script to create CircleCI environments from file in LastPass
Ensure only a single job runs at a time so that there is not a deadlock over BOSH lite environment (in future multiple envs will be enabled)
Update Docker file to include bbl and bosh cli
Ensure space and org created in CF before running test
Run Unit Tests and ATs as seperate steps
Reduce use of .block() in test set up
check environment variables exist before creating in circleci
use springcloudservices docker repo
Use different property prefix for ATs to avoid clashing
Fix deleteServiceInstance so it checks if SI present first to avoid error
Ensure logging is configured to not leak secrets in CircleCI output
Add script to wait for BOSH to release all it's locks before continuing
[closes #45]
This commit is contained in:
Ollie Hughes
2018-08-06 18:04:42 +01:00
committed by Oliver Hughes
parent 3c5417be81
commit 9ba46e1578
27 changed files with 847 additions and 260 deletions

View File

@@ -1,58 +0,0 @@
# Java Gradle CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-java/ for more details
#
version: 2
jobs:
build:
docker:
- image: circleci/openjdk:8-jdk
working_directory: ~/repo
environment:
JAVA_TOOL_OPTIONS: "-Xms512m -Xmx2048m"
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx512m -Xmx2048m" -Dorg.gradle.daemon=false'
TERM: dumb
steps:
- checkout
- restore_cache:
key: sc-app-broker-{{ .Branch }}
- run:
name: "Download dependencies"
command: ./gradlew assemble
- save_cache:
paths:
- ~/.gradle
key: sc-app-broker-{{ .Branch }}
- run:
name: "Disable Gradle daemon"
command: touch ~/.gradle/gradle.properties && echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
- run:
name: "Run tests"
command: ./gradlew clean codeCoverageReport --stacktrace --info --continue
- run:
name: "Aggregate test results"
when: always
command: |
mkdir -p ~/junit/
find . -type f -regex ".*/build/test-results/.*" -exec cp {} ~/junit/ \;
- run:
name: "Upload tests coverage"
command: bash <(curl -s https://codecov.io/bash)
- store_artifacts:
path: ~/junit/
destination: artifacts
- store_test_results:
path: ~/junit/
destination: testartifacts

1
.circleci/config.yml Symbolic link
View File

@@ -0,0 +1 @@
../ci/config.yml

6
.gitignore vendored
View File

@@ -12,3 +12,9 @@ target
bin
.idea
out
.envrc
bbl-state.json
director-vars-store.yml
jumpbox-vars-store.yml
director-vars-file.yml
bbl-env.sh

View File

@@ -306,11 +306,11 @@ configure(rootProject) {
}
pmd {
ruleSetFiles = files("${project.rootDir}/config/pmdRuleSet.xml")
ruleSetFiles = files("${project.rootDir}/ci/config/pmdRuleSet.xml")
}
pmdTest {
ruleSetFiles = files("${project.rootDir}/config/pmdTestRuleSet.xml")
ruleSetFiles = files("${project.rootDir}/ci/config/pmdTestRuleSet.xml")
}
task codeCoverageReport(type: JacocoReport) {

13
ci/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM circleci/openjdk:8-jdk
RUN BOSH_URL="https://s3.amazonaws.com/bosh-cli-artifacts/bosh-cli-5.0.1-linux-amd64"&& \
sudo curl --silent --show-error --location --fail --retry 3 --output /usr/bin/bosh $BOSH_URL && \
sudo chmod +x /usr/bin/bosh && \
bosh -v
RUN BBL_URL="https://github.com/cloudfoundry/bosh-bootloader/releases/download/v6.8.4/bbl-v6.8.4_linux_x86-64" && \
sudo curl --silent --show-error --location --fail --retry 3 --output /usr/bin/bbl $BBL_URL && \
sudo chmod +x /usr/bin/bbl && \
bbl -v
RUN sudo apt-get install python3

50
ci/README.md Normal file
View File

@@ -0,0 +1,50 @@
#App Broker CI Pipeline
### Managing BOSH Lite on GCP
Currently CircleCI is used to run the acceptance tests. The system under test is a BOSH Lite, provisioned using [BOSH Bootloader](https://github.com/cloudfoundry/bosh-bootloader)
The BOSH Lite director can be targeted using following steps;
* Install bosh-cli and bbl
```
$ brew tap cloudfoundry/tap
$ brew install bosh-cli
$ brew install bbl
```
* Setup BBL environment variables
```
lpass show --notes 7083089362665788436 > ci/.envrc
source ci/.envrc
./scripts/populate-secrets-from-environment.sh
./scripts/fix-bbl-state-json-line-breaks.py
cd bbl-bosh-lite-environment/bbl-state
bbl print-env > bbl-env.sh && source bbl-env.sh
bosh deployments
```
### Running CI pipeline locally
CircleCI allows pipelines to be run locally, this is very useful development purposes
* Install circle command line tool to run local builds:
```$bash
curl -o /usr/local/bin/circleci https://circle-downloads.s3.amazonaws.com/releases/build_agent_wrapper/circleci && \
chmod +x /usr/local/bin/circleci
```
* Run script to execute pipeline
`ci/scripts/run-local-ci-job.sh`
### Credentials
CircleCI uses environment variables to store credentials (these are stored inside
a Vault instance within the CircleCI server). Environment variables need to
be uploaded to circle so they can be used by the build.
#### Adding a new variable or credential
* Edit the note in LastPass, adding the new variable:
`lpass edit --notes 7083089362665788436 --sync=now`
* Source all the variables in your local environment
```
lpass show --notes 7083089362665788436 > ci/.envrc
source ci/.envrc
```
* Upload the variables to CircleCi: `ci/scripts/export-envvars-to-cirle.sh`

View File

@@ -0,0 +1,42 @@
{
"version": 14,
"bblVersion": "6.8.4",
"iaas": "gcp",
"id": "<%= bblEnvId %>",
"envID": "appbroker-env",
"noDirector": false,
"aws": {},
"azure": {},
"gcp": {
"zone": "europe-west2-a",
"region": "europe-west2",
"zones": [
"europe-west2-a",
"europe-west2-b",
"europe-west2-c"
]
},
"vsphere": {},
"openstack": {},
"jumpbox": {
"url": "<%= jumpboxUrl %>"
},
"bosh": {
"directorName": "bosh-appbroker-env",
"directorUsername": "<%= directorUsername %>",
"directorPassword": "<%= directorPassword %>",
"directorAddress": "https://<%= directorInternalIp %>:<%= directorPort %>",
"directorSSLCA": <%= directorSSLCA %>,
"directorSSLCertificate": <%= directorSSLCertificate %>,
"directorSSLPrivateKey": <%= directorSSLPrivateKey %>
},
"tfState": "",
"lb": {
"type": "cf",
"cert": <%= lbCert %>,
"key": <%= lbKey %>,
"chain": "",
"domain": "<%= lbDomain %>"
},
"latestTFOutput": ""
}

View File

@@ -0,0 +1 @@
internal_ip: <%= directorInternalIp %>

View File

@@ -0,0 +1,13 @@
admin_password: <%= directorAdminPassword %>
credhub_admin_client_secret: <%= credhubAdminClientSecret %>
credhub_ca:
certificate: |
<%= credhubCA %>
uaa_ssl:
certificate: |
<%= uaaCert %>
jumpbox_ssh:
private_key: |
<%= directorJumpboxSshPrivateKey %>

View File

@@ -0,0 +1,3 @@
jumpbox_ssh:
private_key: |
<%= jumpboxPrivateKey %>

108
ci/config.yml Normal file
View File

@@ -0,0 +1,108 @@
# Java Gradle CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-java/ for more details
#
version: 2
jobs:
build:
docker:
- image: springcloudservices/spring-cloud-app-broker-ci:v1
working_directory: ~/repo
environment:
JAVA_TOOL_OPTIONS: "-Xms512m -Xmx2048m"
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx512m -Xmx2048m" -Dorg.gradle.daemon=false'
TERM: dumb
steps:
- checkout
- restore_cache:
key: sc-app-broker-{{ .Branch }}
- run:
name: "Queue until all other builds finished"
command: |
cd ~/repo/ci/scripts
./wait-for-other-builds-to-finish.sh 60
- run:
name: "Download dependencies"
command: ./gradlew assemble
- save_cache:
paths:
- ~/.gradle
key: sc-app-broker-{{ .Branch }}
- run:
name: "Disable Gradle daemon"
command: touch ~/.gradle/gradle.properties && echo "org.gradle.daemon=false" >> ~/.gradle/gradle.properties
- run:
name: "Run unit tests"
command: ./gradlew clean codeCoverageReport --stacktrace
- run:
name: "Aggregate test results"
when: always
command: |
mkdir -p ~/junit/
find . -type f -regex ".*/build/test-results/.*" -exec cp {} ~/junit/ \;
- run:
name: "Upload tests coverage"
command: bash <(curl -s https://codecov.io/bash)
- store_artifacts:
path: ~/junit/
destination: artifacts
- store_test_results:
path: ~/junit/
destination: testartifacts
- run:
name: "Populate Secrets from Environment"
command: |
if [[ ${CIRCLE_BUILD_NUM} = "" ]]; then
echo "populating from .envrc"
[[ -f "ci/.envrc" ]] || (printf "Download .envrc from LastPass using \"lpass show --notes 7083089362665788436\" in directory ${CI_DIR} before running script"; exit 1)
cd ~/repo/ci/
cat .envrc >> $BASH_ENV
./scripts/populate-secrets-from-environment.sh
./scripts/fix-bbl-state-json-line-breaks.py
cd bbl-bosh-lite-environment/bbl-state
bbl print-env >> $BASH_ENV
else
echo "populating from bbl env"
cd ~/repo/ci/scripts
./populate-secrets-from-environment.sh
cd ~/repo/ci/bbl-bosh-lite-environment/bbl-state
bbl print-env >> $BASH_ENV
fi
- run:
name: "Start BOSH Lite"
command: |
cd ~/repo/ci
./scripts/wait-for-bosh-locks-to-be-released.sh 30
bosh -n -d cf start
- run:
name: "Run acceptance tests"
command: |
./gradlew -b spring-cloud-app-broker-sample/build.gradle assemble
./gradlew clean assemble check -PacceptanceTests --stacktrace --continue -b spring-cloud-app-broker-acceptance-tests/build.gradle
- store_test_results:
path: ~/repo/spring-cloud-app-broker-acceptance-tests/build/test-results
destination: acceptancetestartifacts
- run:
name: "Stop BOSH Lite"
command: |
cd ~/repo/ci/bbl-bosh-lite-environment/bbl-state
bosh -n -d cf stop
when: always

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
#TODO: List all envvars in Circle then delete to avoid needing to explicitly name all vars
set -euo pipefail
readonly CIRCLE_API_BASE=https://circleci.com/api/v1.1/project/github/spring-cloud-incubator/spring-cloud-app-broker
delete_envvars(){
delete_envvar_from_circle DIRECTOR_SSLCA
delete_envvar_from_circle JUMPBOX_PRIVATE_KEY
delete_envvar_from_circle UAA_CERT
delete_envvar_from_circle CREDHUB_CA
delete_envvar_from_circle LB_KEY
delete_envvar_from_circle LB_CERT
delete_envvar_from_circle DIRECTOR_SSLPRIVATE_KEY
delete_envvar_from_circle DIRECTOR_SSLCERTIFICATE
delete_envvar_from_circle DIRECTOR_JUMPBOX_SSH_PRIVATE_KEY
delete_envvar_from_circle DIRECTOR_USERNAME
delete_envvar_from_circle DIRECTOR_PASSWORD
delete_envvar_from_circle DIRECTOR_INTERNAL_IP
delete_envvar_from_circle DIRECTOR_PORT
delete_envvar_from_circle JUMPBOX_URL
delete_envvar_from_circle LB_DOMAIN
delete_envvar_from_circle DIRECTOR_ADMIN_PASSWORD
delete_envvar_from_circle CREDHUB_ADMIN_CLIENT_SECRET
delete_envvar_from_circle BBL_ENV_ID
delete_envvar_from_circle CIRCLECI_API_KEY
delete_envvar_from_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_API_HOST
delete_envvar_from_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_API_PORT
delete_envvar_from_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_USERNAME
delete_envvar_from_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_PASSWORD
delete_envvar_from_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_DEFAULT_ORG
delete_envvar_from_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_DEFAULT_SPACE
delete_envvar_from_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_SKIP_SSL_VALIDATION
delete_envvar_from_circle TESTS_SAMPLEBROKERAPPPATH
}
delete_envvar_from_circle() {
local envvar_name=$1
curl -XDELETE "${CIRCLE_API_BASE}/envvar/${envvar_name}?circle-token=${CIRCLECI_API_KEY}"
}
delete_envvars

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
readonly CIRCLE_API_BASE=https://circleci.com/api/v1.1/project/github/spring-cloud-incubator/spring-cloud-app-broker
readonly PROG_DIR=$(readlink -m $(dirname $0))
readonly CI_DIR=$(dirname "${PROG_DIR}")
export_variables_to_circle() {
[[ ! -f "${CI_DIR}/.envrc" ]] && printf "Download .envrc from LastPass using \"lpass show --notes 7083089362665788436\" in directory ${SCRIPTS_DIR} before running script" && exit 1
: ${DIRECTOR_SSLCA:?"Required Env Variable not found!"}
: ${JUMPBOX_PRIVATE_KEY:?"Required Env Variable not found!"}
: ${UAA_CERT:?"Required Env Variable not found!"}
: ${CREDHUB_CA:?"Required Env Variable not found!"}
: ${LB_KEY:?"Required Env Variable not found!"}
: ${LB_CERT:?"Required Env Variable not found!"}
: ${DIRECTOR_SSLPRIVATE_KEY:?"Required Env Variable not found!"}
: ${DIRECTOR_SSLCERTIFICATE:?"Required Env Variable not found!"}
: ${DIRECTOR_SSLCERTIFICATE:?"Required Env Variable not found!"}
: ${DIRECTOR_JUMPBOX_SSH_PRIVATE_KEY:?"Required Env Variable not found!"}
: ${DIRECTOR_USERNAME:?"Required Env Variable not found!"}
: ${DIRECTOR_PASSWORD:?"Required Env Variable not found!"}
: ${DIRECTOR_INTERNAL_IP:?"Required Env Variable not found!"}
: ${DIRECTOR_PORT:?"Required Env Variable not found!"}
: ${JUMPBOX_URL:?"Required Env Variable not found!"}
: ${LB_DOMAIN:?"Required Env Variable not found!"}
: ${DIRECTOR_ADMIN_PASSWORD:?"Required Env Variable not found!"}
: ${CREDHUB_ADMIN_CLIENT_SECRET:?"Required Env Variable not found!"}
: ${BBL_ENV_ID:?"Required Env Variable not found!"}
: ${CIRCLECI_API_KEY:?"Required Env Variable not found!"}
: ${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_API_HOST:?"Required Env Variable not found!"}
: ${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_API_PORT:?"Required Env Variable not found!"}
: ${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_USERNAME:?"Required Env Variable not found!"}
: ${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_PASSWORD:?"Required Env Variable not found!"}
: ${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_DEFAULT_ORG:?"Required Env Variable not found!"}
: ${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_DEFAULT_SPACE:?"Required Env Variable not found!"}
: ${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_SKIP_SSL_VALIDATION:?"Required Env Variable not found!"}
: ${TESTS_SAMPLEBROKERAPPPATH:?"Required Env Variable not found!"}
create_envvar_in_circle DIRECTOR_SSLCA "${DIRECTOR_SSLCA}"
create_envvar_in_circle JUMPBOX_PRIVATE_KEY "${JUMPBOX_PRIVATE_KEY}"
create_envvar_in_circle UAA_CERT "${UAA_CERT}"
create_envvar_in_circle CREDHUB_CA "${CREDHUB_CA}"
create_envvar_in_circle LB_KEY "${LB_KEY}"
create_envvar_in_circle LB_CERT "${LB_CERT}"
create_envvar_in_circle DIRECTOR_SSLPRIVATE_KEY "${DIRECTOR_SSLPRIVATE_KEY}"
create_envvar_in_circle DIRECTOR_SSLCERTIFICATE "${DIRECTOR_SSLCERTIFICATE}"
create_envvar_in_circle DIRECTOR_JUMPBOX_SSH_PRIVATE_KEY "${DIRECTOR_JUMPBOX_SSH_PRIVATE_KEY}"
create_envvar_in_circle DIRECTOR_USERNAME "${DIRECTOR_USERNAME}"
create_envvar_in_circle DIRECTOR_PASSWORD "${DIRECTOR_PASSWORD}"
create_envvar_in_circle DIRECTOR_INTERNAL_IP "${DIRECTOR_INTERNAL_IP}"
create_envvar_in_circle DIRECTOR_PORT "${DIRECTOR_PORT}"
create_envvar_in_circle JUMPBOX_URL "${JUMPBOX_URL}"
create_envvar_in_circle LB_DOMAIN "${LB_DOMAIN}"
create_envvar_in_circle DIRECTOR_ADMIN_PASSWORD "${DIRECTOR_ADMIN_PASSWORD}"
create_envvar_in_circle CREDHUB_ADMIN_CLIENT_SECRET "${CREDHUB_ADMIN_CLIENT_SECRET}"
create_envvar_in_circle BBL_ENV_ID "${BBL_ENV_ID}"
create_envvar_in_circle CIRCLECI_API_KEY "${CIRCLECI_API_KEY}"
create_envvar_in_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_API_HOST "${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_API_HOST}"
create_envvar_in_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_API_PORT "${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_API_PORT}"
create_envvar_in_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_USERNAME "${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_USERNAME}"
create_envvar_in_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_PASSWORD "${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_PASSWORD}"
create_envvar_in_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_DEFAULT_ORG "${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_DEFAULT_ORG}"
create_envvar_in_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_DEFAULT_SPACE "${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_DEFAULT_SPACE}"
create_envvar_in_circle SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_SKIP_SSL_VALIDATION "${SPRING_CLOUD_APPBROKER_ACCEPTANCETEST_CLOUDFOUNDRY_SKIP_SSL_VALIDATION}"
create_envvar_in_circle TESTS_SAMPLEBROKERAPPPATH "${TESTS_SAMPLEBROKERAPPPATH}"
}
create_envvar_in_circle() {
local envvar_name=$1
local envvar_value=$2
curl -XPOST "${CIRCLE_API_BASE}/envvar?circle-token=${CIRCLECI_API_KEY}" \
-H "Content-Type: application/json" -d '{"name": "'"${envvar_name}"'", "value": "'"${envvar_value}"'"}'
}
export_variables_to_circle

View File

@@ -0,0 +1,12 @@
#!/usr/bin/env python3
# I couldn't get this working with sed so python it is!
import fileinput
import os
import sys
script_path = os.path.dirname(os.path.realpath(sys.argv[0]))
ci_dir = os.path.dirname(script_path)
with fileinput.FileInput(os.path.join(ci_dir, 'bbl-bosh-lite-environment/bbl-state/bbl-state.json'), inplace=True) as file:
for line in file:
print(line.replace('\\\\n', '\\n'), end='')

View File

@@ -0,0 +1,48 @@
#!/bin/bash
set -euo pipefail
readonly PROG_DIR=$(readlink -m $(dirname $0))
readonly SCRIPTS_DIR=$(dirname "${PROG_DIR}")
readonly CI_DIR=$(dirname "${SCRIPTS_DIR}")
cd "${SCRIPTS_DIR}/bbl-bosh-lite-environment/bbl-state/"
substitute_bbl_state_file(){
perl -pe "s|<%= bblEnvId %>|${BBL_ENV_ID}|;" \
-pe "s|<%= jumpboxUrl %>|${JUMPBOX_URL}|;" \
-pe "s|<%= directorUsername %>|${DIRECTOR_USERNAME}|;" \
-pe "s|<%= directorPassword %>|${DIRECTOR_PASSWORD}|;" \
-pe "s|<%= directorInternalIp %>|${DIRECTOR_INTERNAL_IP}|;" \
-pe "s|<%= directorPort %>|${DIRECTOR_PORT}|;" \
-pe "s|<%= directorSSLCA %>|$(echo ${DIRECTOR_SSLCA} | jq -aR .)|;" \
-pe "s|<%= directorSSLCertificate %>|$(echo ${DIRECTOR_SSLCERTIFICATE} | jq -aR .)|;" \
-pe "s|<%= directorSSLPrivateKey %>|$(echo ${DIRECTOR_SSLPRIVATE_KEY} | jq -aR .)|;" \
-pe "s|<%= lbCert %>|$(echo ${LB_CERT} | jq -aR .)|;" \
-pe "s|<%= lbKey %>|$(echo ${LB_KEY} | jq -aR .)|;" \
-pe "s|<%= lbDomain %>|${LB_DOMAIN}|;" \
bbl-state.json.tmpl > bbl-state.json
}
substitute_director_vars_store(){
perl -pe "s|<%= directorAdminPassword %>|${DIRECTOR_ADMIN_PASSWORD}|;" \
-pe "s|<%= credhubAdminClientSecret %>|${CREDHUB_ADMIN_CLIENT_SECRET}|;" \
-pe "s|<%= credhubCA %>|${CREDHUB_CA}|;" \
-pe "s|<%= uaaCert %>|${UAA_CERT}|;" \
-pe "s|<%= directorJumpboxSshPrivateKey %>|${DIRECTOR_JUMPBOX_SSH_PRIVATE_KEY}|;" \
director-vars-store.yml.tmpl > vars/director-vars-store.yml
}
substitute_director_vars_file(){
perl -pe "s|<%= directorInternalIp %>|${DIRECTOR_INTERNAL_IP}|;" \
director-vars-file.yml.tmpl > vars/director-vars-file.yml
}
substitute_jumpbox_vars_store(){
perl -pe "s|<%= jumpboxPrivateKey %>|${JUMPBOX_PRIVATE_KEY}|;" \
jumpbox-vars-store.yml.tmpl > vars/jumpbox-vars-store.yml
}
substitute_bbl_state_file
substitute_director_vars_store
substitute_director_vars_file
substitute_jumpbox_vars_store

27
ci/scripts/run-local-ci-job.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
readonly PROG_DIR=$(readlink -m $(dirname $0))
readonly CI_DIR=$(dirname "${PROG_DIR}")
readonly PROJECT_ROOT=$(dirname "${CI_DIR}")
validate(){
[[ $(type circleci) ]] || (echo "circleci tool not installed" >&2 ; circle_usage ; exit 2)
[[ -f "${CI_DIR}/.envrc" ]] || (printf "Download .envrc from LastPass using \"lpass show --notes 7083089362665788436\" in directory ${CI_DIR} before running script"; exit 1)
}
circle_usage() {
cat <<- EOF
Install circle command line tool to run local builds:
"curl -o /usr/local/bin/circleci https://circle-downloads.s3.amazonaws.com/releases/build_agent_wrapper/circleci && \
chmod +x /usr/local/bin/circleci"
EOF
}
run_circle_command(){
cd "${PROJECT_ROOT}"
circleci build
}
validate
run_circle_command

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# The BOSH director might be in use by someone else. Check `bosh locks` command before running ATs
set -euo pipefail
[[ -z "${1:-}" ]] && printf "Must provide Max Queue Time in *minutes* as script argument\n" && exit 1
readonly MAX_TIME=${1}
wait_for_bosh_locks_to_be_released(){
local max_time_seconds=$((MAX_TIME * 60))
until [[ $(bosh locks --json | jq '.Tables[].Rows | length') -eq 0 ]]; do
sleep 10
printf "."
if [[ "${SECONDS}" -gt "${max_time_seconds}" ]]; then
# After cancelling the build, wait some time to ensure the job has been cancelled otherwise
# the script will exit and the job will be incorrectly marked success or failed
printf "Max time exceeded waiting for bosh locks to be released, cancelling build\n"
cancel_current_build
sleep 20
exit 1
fi
done
printf "No BOSH locks found, continuing build\n"
}
cancel_current_build() {
curl -s -X POST \
"https://circleci.com/api/v1.1/project/github/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BUILD_NUM}/cancel?circle-token=${CIRCLECI_API_KEY}" > /dev/null
}
wait_for_bosh_locks_to_be_released

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
[[ -z "${1:-}" ]] && printf "Must provide Max Queue Time in *minutes* as script argument\n" && exit 1
[[ -z "${CIRCLE_BUILD_NUM:-}" ]] && printf "skipping, not running on remote circleci environment\n" && exit 0
: ${CIRCLECI_API_KEY:?"Required Env Variable not found!"}
: ${CIRCLE_PROJECT_USERNAME:?"Required Env Variable not found!"}
: ${CIRCLE_PROJECT_REPONAME:?"Required Env Variable not found!"}
: ${CIRCLE_REPOSITORY_URL:?"Required Env Variable not found!"}
readonly MAX_TIME=${1}
wait_for_previous_builds_to_complete(){
local max_time_seconds=$((MAX_TIME * 60))
until [[ $(curl -s "https://circleci.com/api/v1.1/project/github/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}?circle-token=${CIRCLECI_API_KEY}&filter=running" \
| jq 'min_by(.build_num) | .build_num') \
-eq ${CIRCLE_BUILD_NUM} ]]; do
sleep 10
printf "."
if [[ "${SECONDS}" -gt "${max_time_seconds}" ]]; then
# After cancelling the build, wait some time to ensure the job has been cancelled otherwise
# the script will exit and the job will be incorrectly marked success or failed
printf "Max time exceeded waiting to reach front of queue, cancelling build\n"
cancel_current_build
sleep 20
exit 1
fi
done
printf "Build is front of queue\n"
}
cancel_current_build() {
curl -s -X POST \
"https://circleci.com/api/v1.1/project/github/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BUILD_NUM}/cancel?circle-token=${CIRCLECI_API_KEY}" > /dev/null
}
wait_for_previous_builds_to_complete

View File

@@ -12,14 +12,14 @@
The tests require the following properties to be set:
* `tests.sampleAppPath` - The path to the sample broker jar (eg. /spring-cloud-app-broker-sample/build/libs/spring-cloud-app-broker-sample.jar).
* `spring.cloud.appbroker.deployer.cloudfoundry.api-host` - The CF API host where the tests are going to run.
* `spring.cloud.appbroker.deployer.cloudfoundry.api-port` - The CF API port where the tests are going to run.
* `spring.cloud.appbroker.deployer.cloudfoundry.username` - The CF API username where the tests are going to run.
* `spring.cloud.appbroker.deployer.cloudfoundry.password` - The CF API password where the tests are going to run.
* `spring.cloud.appbroker.deployer.cloudfoundry.default-org` - The CF organization where the tests are going to run.
* `spring.cloud.appbroker.deployer.cloudfoundry.default-space` - The CF space where the tests are going to run.
* `spring.cloud.appbroker.deployer.cloudfoundry.skip-ssl-validation` - If SSL validation should be skipped.
* `tests.sampleBrokerAppPath` - The path to the sample broker jar (eg. /spring-cloud-app-broker-sample/build/libs/spring-cloud-app-broker-sample.jar).
* `spring.cloud.appbroker.acceptance-test.cloudfoundry.api-host` - The CF API host where the tests are going to run.
* `spring.cloud.appbroker.acceptance-test.cloudfoundry.api-port` - The CF API port where the tests are going to run.
* `spring.cloud.appbroker.acceptance-test.cloudfoundry.username` - The CF API username where the tests are going to run.
* `spring.cloud.appbroker.acceptance-test.cloudfoundry.password` - The CF API password where the tests are going to run.
* `spring.cloud.appbroker.acceptance-test.cloudfoundry.default-org` - The CF organization where the tests are going to run.
* `spring.cloud.appbroker.acceptance-test.cloudfoundry.default-space` - The CF space where the tests are going to run.
* `spring.cloud.appbroker.acceptance-test.cloudfoundry.skip-ssl-validation` - If SSL validation should be skipped.
These properties can be set with `-D` system properties on the gradle command line.
@@ -28,11 +28,11 @@ Also, the flag acceptanceTests must be provided as `-PacceptanceTests`.
[source,bash]
----
$ ./gradlew test -PacceptanceTests \
-Dtests.sampleAppPath=/path/to/broker.jar \
-Dspring.cloud.appbroker.deployer.cloudfoundry.api-host=api.cf.my.cf.io \
-Dspring.cloud.appbroker.deployer.cloudfoundry.api-port=443 \
-Dspring.cloud.appbroker.deployer.cloudfoundry.username=admin \
-Dspring.cloud.appbroker.deployer.cloudfoundry.password=password \
-Dspring.cloud.appbroker.deployer.cloudfoundry.default-org=test \
-Dspring.cloud.appbroker.deployer.cloudfoundry.default-space=development \
-Dspring.cloud.appbroker.deployer.cloudfoundry.skip-ssl-validation=true
-Dtests.sampleBrokerAppPath=/path/to/broker.jar \
-Dspring.cloud.appbroker.acceptance-test.cloudfoundry.api-host=api.cf.my.cf.io \
-Dspring.cloud.appbroker.acceptance-test.cloudfoundry.api-port=443 \
-Dspring.cloud.appbroker.acceptance-test.cloudfoundry.username=admin \
-Dspring.cloud.appbroker.acceptance-test.cloudfoundry.password=password \
-Dspring.cloud.appbroker.acceptance-test.cloudfoundry.default-org=test \
-Dspring.cloud.appbroker.acceptance-test.cloudfoundry.default-space=development \
-Dspring.cloud.appbroker.acceptance-test.cloudfoundry.skip-ssl-validation=true

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2016-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
package org.springframework.cloud.appbroker.acceptance;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(value = "tests")
public class AcceptanceTestProperties {
private String sampleBrokerAppPath;
public String getSampleBrokerAppPath() {
return sampleBrokerAppPath;
}
public void setSampleBrokerAppPath(String sampleBrokerAppPath) {
this.sampleBrokerAppPath = sampleBrokerAppPath;
}
}

View File

@@ -18,15 +18,19 @@ package org.springframework.cloud.appbroker.acceptance;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
import org.cloudfoundry.operations.applications.ApplicationSummary;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.appbroker.acceptance.fixtures.cf.CloudFoundryClientConfiguration;
import org.springframework.cloud.appbroker.acceptance.fixtures.cf.CloudFoundryService;
@@ -35,6 +39,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
@SpringBootTest(classes = {CloudFoundryClientConfiguration.class, CloudFoundryService.class})
@ExtendWith(SpringExtension.class)
@ExtendWith(BrokerPropertiesParameterResolver.class)
@EnableConfigurationProperties(AcceptanceTestProperties.class)
class CloudFoundryAcceptanceTest {
@BeforeEach
@@ -43,7 +48,7 @@ class CloudFoundryAcceptanceTest {
}
private static final String SAMPLE_BROKER_APP_NAME = "sample-broker";
private static final String BROKER_NAME = "sample-broker-name";
private static final String SERVICE_BROKER_NAME = "sample-broker-name";
private static final String SERVICE_NAME = "example";
private static final String PLAN_NAME = "standard";
private static final String SERVICE_INSTANCE_NAME = "my-service";
@@ -51,53 +56,68 @@ class CloudFoundryAcceptanceTest {
@Autowired
private CloudFoundryService cloudFoundryService;
@Value("${tests.sampleAppPath}")
private String sampleAppPath;
@Autowired
private AcceptanceTestProperties acceptanceTestProperties;
@AfterEach
void tearDown() {
cleanup();
blockingSubscribe(cleanup());
}
void initializeBroker(String[] properties) {
cleanup();
void initializeBroker(String[] backingAppProperties) {
cloudFoundryService.pushAppNoStart(SAMPLE_BROKER_APP_NAME, getSampleAppPath());
cloudFoundryService.setBrokerAppEnvironment(properties);
cloudFoundryService.startApplication(SAMPLE_BROKER_APP_NAME);
String backingAppURL = cloudFoundryService.getApplicationRoute(SAMPLE_BROKER_APP_NAME);
cloudFoundryService.createServiceBroker(BROKER_NAME, backingAppURL);
cloudFoundryService.enableServiceBrokerAccess(SERVICE_NAME);
blockingSubscribe(cloudFoundryService
.getOrCreateDefaultOrganization()
.then(cloudFoundryService.getOrCreateDefaultSpace())
.then(cloudFoundryService.pushAppBroker(SAMPLE_BROKER_APP_NAME, getSampleBrokerAppPath(), backingAppProperties))
.then(cloudFoundryService.createServiceBroker(SERVICE_BROKER_NAME, SAMPLE_BROKER_APP_NAME))
.then(cloudFoundryService.enableServiceBrokerAccess(SERVICE_NAME)));
}
private void cleanup() {
cloudFoundryService.deleteServiceInstance(SERVICE_INSTANCE_NAME);
cloudFoundryService.deleteServiceBroker(BROKER_NAME);
cloudFoundryService.deleteBackingApp(SAMPLE_BROKER_APP_NAME);
private Mono<Void> cleanup() {
return cloudFoundryService
.deleteServiceInstance(SERVICE_INSTANCE_NAME)
.then(cloudFoundryService.deleteServiceBroker(SERVICE_BROKER_NAME))
.then(cloudFoundryService.deleteBackingApp(SAMPLE_BROKER_APP_NAME));
}
void createServiceInstance() {
cloudFoundryService.createServiceInstance(PLAN_NAME, SERVICE_NAME, SERVICE_INSTANCE_NAME);
blockingSubscribe(cloudFoundryService.createServiceInstance(PLAN_NAME, SERVICE_NAME, SERVICE_INSTANCE_NAME));
}
void deleteServiceInstance() {
cloudFoundryService.deleteServiceInstance(SERVICE_INSTANCE_NAME);
blockingSubscribe(cloudFoundryService.deleteServiceInstance(SERVICE_INSTANCE_NAME));
}
Optional<ApplicationSummary> getApplicationSummaryByName(String appName) {
List<ApplicationSummary> applicationsAfterDeletion = cloudFoundryService.getApplications();
return applicationsAfterDeletion.stream()
return cloudFoundryService
.getApplications()
.flatMapMany(Flux::fromIterable)
.filter(applicationSummary -> appName.equals(applicationSummary.getName()))
.findFirst();
.next()
.blockOptional();
}
ApplicationEnvironments getApplicationEnvironmentByName(String appName) {
return cloudFoundryService.getApplicationEnvironmentByAppName(appName);
return cloudFoundryService.getApplicationEnvironmentByAppName(appName).block();
}
private Path getSampleAppPath() {
return Paths.get(sampleAppPath, "");
private Path getSampleBrokerAppPath() {
return Paths.get(acceptanceTestProperties.getSampleBrokerAppPath(), "");
}
private <T> void blockingSubscribe(Mono<? super T> publisher){
CountDownLatch latch = new CountDownLatch(1);
publisher.subscribe(System.out::println, t -> {
t.printStackTrace();
latch.countDown();
}, latch::countDown);
try {
latch.await();
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -17,16 +17,17 @@
package org.springframework.cloud.appbroker.acceptance.fixtures.cf;
import java.net.URI;
import org.cloudfoundry.reactor.ProxyConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.cloudfoundry.reactor.ProxyConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import static org.springframework.cloud.appbroker.acceptance.fixtures.cf.CloudFoundryProperties.PROPERTY_PREFIX;
@ConfigurationProperties(PROPERTY_PREFIX)
public class CloudFoundryProperties {
static final String PROPERTY_PREFIX = "spring.cloud.appbroker.deployer.cloudfoundry";
static final String PROPERTY_PREFIX = "spring.cloud.appbroker.acceptancetest.cloudfoundry";
private String apiHost;
private Integer apiPort;

View File

@@ -18,194 +18,263 @@ package org.springframework.cloud.appbroker.acceptance.fixtures.cf;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Map;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
import org.cloudfoundry.operations.applications.ApplicationManifest;
import org.cloudfoundry.operations.applications.ApplicationSummary;
import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest;
import org.cloudfoundry.operations.applications.GetApplicationRequest;
import org.cloudfoundry.operations.applications.PushApplicationRequest;
import org.cloudfoundry.operations.applications.SetEnvironmentVariableApplicationRequest;
import org.cloudfoundry.operations.applications.StartApplicationRequest;
import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
import org.cloudfoundry.operations.organizations.CreateOrganizationRequest;
import org.cloudfoundry.operations.organizations.DefaultOrganizations;
import org.cloudfoundry.operations.organizations.OrganizationSummary;
import org.cloudfoundry.operations.serviceadmin.CreateServiceBrokerRequest;
import org.cloudfoundry.operations.serviceadmin.DeleteServiceBrokerRequest;
import org.cloudfoundry.operations.serviceadmin.EnableServiceAccessRequest;
import org.cloudfoundry.operations.services.CreateServiceInstanceRequest;
import org.cloudfoundry.operations.services.DeleteServiceInstanceRequest;
import org.cloudfoundry.operations.spaces.CreateSpaceRequest;
import org.cloudfoundry.operations.spaces.DefaultSpaces;
import org.cloudfoundry.operations.spaces.SpaceSummary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.appbroker.acceptance.AcceptanceTestProperties;
import org.springframework.stereotype.Service;
import static java.lang.String.format;
@Service
public class CloudFoundryService {
private static final Logger LOGGER = LoggerFactory.getLogger(CloudFoundryService.class);
@Autowired
private CloudFoundryOperations cloudFoundryOperations;
private CloudFoundryProperties cloudFoundryProperties;
private CloudFoundryClient cloudFoundryClient;
private AcceptanceTestProperties acceptanceTestProperties;
@Autowired
private CloudFoundryProperties cloudFoundryProperties;
public void enableServiceBrokerAccess(String serviceName) {
cloudFoundryOperations
.serviceAdmin()
.enableServiceAccess(EnableServiceAccessRequest.builder().serviceName(serviceName).build())
.block();
public CloudFoundryService(CloudFoundryOperations cloudFoundryOperations, CloudFoundryProperties cloudFoundryProperties,
CloudFoundryClient cloudFoundryClient, AcceptanceTestProperties acceptanceTestProperties) {
this.cloudFoundryOperations = cloudFoundryOperations;
this.cloudFoundryProperties = cloudFoundryProperties;
this.cloudFoundryClient = cloudFoundryClient;
this.acceptanceTestProperties = acceptanceTestProperties;
}
public void createServiceBroker(String brokerName, String backingAppURL) {
cloudFoundryOperations
.serviceAdmin()
.create(CreateServiceBrokerRequest.builder()
.name(brokerName)
.username("user")
.password("password")
.url(backingAppURL)
.build())
.block();
}
public String getApplicationRoute(String appName) {
return "https://" + cloudFoundryOperations
.applications()
.get(GetApplicationRequest.builder().name(appName).build())
.block().getUrls().get(0);
}
public void startApplication(String appName) {
cloudFoundryOperations
.applications()
.start(StartApplicationRequest.builder().name(appName).build())
.block();
}
public void pushAppNoStart(String appName, Path appPath) {
cloudFoundryOperations
.applications()
.push(PushApplicationRequest
.builder()
.noStart(true)
.path(appPath)
.name(appName)
.build())
.block();
}
public void deleteBackingApp(String appName) {
try {
cloudFoundryOperations
.applications()
.delete(DeleteApplicationRequest.builder().name(appName).build())
.block();
} catch (Exception e) {
// Ignore
}
}
public void deleteServiceBroker(String brokerName) {
try {
public Mono<Void> enableServiceBrokerAccess(String serviceName) {
return loggingMono(
cloudFoundryOperations
.serviceAdmin()
.delete(DeleteServiceBrokerRequest.builder().name(brokerName).build())
.block();
} catch (Exception e) {
// Ignore
}
.enableServiceAccess(EnableServiceAccessRequest.builder().serviceName(serviceName).build()));
}
public void deleteServiceInstance(String serviceInstanceName) {
try {
public Mono<Void> createServiceBroker(String brokerName, String sampleBrokerAppName) {
return loggingMono(
getApplicationRoute(sampleBrokerAppName)
.flatMap(url -> cloudFoundryOperations
.serviceAdmin()
.create(CreateServiceBrokerRequest
.builder()
.name(brokerName)
.username("user")
.password("password")
.url(url)
.build())));
}
public Mono<String> getApplicationRoute(String appName) {
return loggingMono(
cloudFoundryOperations
.applications()
.get(GetApplicationRequest.builder().name(appName).build())
.map(ApplicationDetail::getUrls)
.flatMapMany(Flux::fromIterable)
.next()
.map(url -> "https://" + url));
}
public Mono<Void> pushAppBroker(String appName, Path appPath, String[] backingAppProperties) {
return loggingMono(
cloudFoundryOperations
.applications()
.pushManifest(PushApplicationManifestRequest
.builder()
.manifest(ApplicationManifest
.builder()
.putAllEnvironmentVariables(appBrokerDeployerEnvironmentVariables())
.putAllEnvironmentVariables(appBrokerCatalogEnvironmentVariables())
.putAllEnvironmentVariables(backingAppEnvironmentVariables(backingAppProperties))
.name(appName)
.path(appPath)
.memory(1024)
.build())
.build()));
}
public Mono<Void> deleteBackingApp(String appName) {
return loggingMono(
cloudFoundryOperations
.applications()
.delete(DeleteApplicationRequest.builder().name(appName).build()));
}
public Mono<Void> deleteServiceBroker(String brokerName) {
return loggingMono(
cloudFoundryOperations
.serviceAdmin()
.delete(DeleteServiceBrokerRequest.builder().name(brokerName).build()));
}
public Mono<Void> deleteServiceInstance(String serviceInstanceName) {
return loggingMono(
cloudFoundryOperations
.services()
.deleteInstance(DeleteServiceInstanceRequest.builder().name(serviceInstanceName).build())
.block();
} catch (Exception e) {
// Ignore
.listInstances()
.filter(si -> si.getName().equals(serviceInstanceName))
.next()
.flatMap(si ->
cloudFoundryOperations
.services()
.deleteInstance(DeleteServiceInstanceRequest.builder().name(si.getName()).build())));
}
public Mono<Void> createServiceInstance(String planName, String serviceName, String serviceInstanceName) {
return loggingMono(
cloudFoundryOperations
.services()
.createInstance(CreateServiceInstanceRequest
.builder()
.planName(planName)
.serviceName(serviceName)
.serviceInstanceName(serviceInstanceName)
.build()));
}
public Mono<List<ApplicationSummary>> getApplications() {
return loggingMono(
cloudFoundryOperations.applications().list().collectList());
}
public Mono<ApplicationEnvironments> getApplicationEnvironmentByAppName(String appName) {
return loggingMono(
cloudFoundryOperations
.applications()
.getEnvironments(GetApplicationEnvironmentsRequest.builder().name(appName).build()));
}
public Mono<SpaceSummary> getOrCreateDefaultSpace() {
final String defaultOrg = cloudFoundryProperties.getDefaultOrg();
DefaultSpaces spaceOperations = new DefaultSpaces(
Mono.just(cloudFoundryClient),
getOrCreateDefaultOrganization().map(OrganizationSummary::getId),
Mono.just(cloudFoundryProperties.getUsername()));
final String defaultSpace = cloudFoundryProperties.getDefaultSpace();
return loggingMono(
getDefaultSpace(spaceOperations)
.switchIfEmpty(spaceOperations
.create(CreateSpaceRequest
.builder()
.name(defaultSpace)
.organization(defaultOrg)
.build())
.then(getDefaultSpace(spaceOperations))));
}
public Mono<OrganizationSummary> getOrCreateDefaultOrganization() {
DefaultOrganizations organizationOperations = new DefaultOrganizations(
Mono.just(cloudFoundryClient),
Mono.just(cloudFoundryProperties.getUsername()));
final String defaultOrg = cloudFoundryProperties.getDefaultOrg();
return loggingMono(
getDefaultOrg(organizationOperations)
.switchIfEmpty(organizationOperations
.create(CreateOrganizationRequest
.builder()
.organizationName(defaultOrg)
.build())
.then(getDefaultOrg(organizationOperations))));
}
private Mono<OrganizationSummary> getDefaultOrg(DefaultOrganizations orgOperations) {
return orgOperations
.list()
.filter(r -> r
.getName()
.equals(cloudFoundryProperties.getDefaultOrg()))
.next();
}
private Mono<SpaceSummary> getDefaultSpace(DefaultSpaces spaceOperations) {
return spaceOperations
.list()
.filter(r -> r
.getName()
.equals(cloudFoundryProperties.getDefaultSpace()))
.next();
}
private Map<String, String> appBrokerDeployerEnvironmentVariables() {
Map<String, String> deployerVariables = new HashMap<>();
deployerVariables.put("spring.cloud.appbroker.deployer.cloudfoundry.api-host", cloudFoundryProperties.getApiHost());
deployerVariables.put("spring.cloud.appbroker.deployer.cloudfoundry.api-port", String.valueOf(cloudFoundryProperties.getApiPort()));
deployerVariables.put("spring.cloud.appbroker.deployer.cloudfoundry.username", cloudFoundryProperties.getUsername());
deployerVariables.put("spring.cloud.appbroker.deployer.cloudfoundry.password", cloudFoundryProperties.getPassword());
deployerVariables.put("spring.cloud.appbroker.deployer.cloudfoundry.default-org", cloudFoundryProperties.getDefaultOrg());
deployerVariables.put("spring.cloud.appbroker.deployer.cloudfoundry.default-space", cloudFoundryProperties.getDefaultSpace());
deployerVariables.put("spring.cloud.appbroker.deployer.cloudfoundry.skip-ssl-validation", String.valueOf(cloudFoundryProperties.isSkipSslValidation()));
deployerVariables.put("spring.cloud.appbroker.apps[0].cloudfoundry.skip-ssl-validation", String.valueOf(cloudFoundryProperties.isSkipSslValidation()));
return deployerVariables;
}
private Map<String, String> appBrokerCatalogEnvironmentVariables() {
Map<String, String> catalogVariables = new HashMap<>();
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].id", "example-service");
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].name", "example");
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].description", "A simple example");
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].bindable", "true");
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].tags[0]", "example");
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].plans[0].id", "simple-plan");
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].plans[0].bindable", "true");
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].plans[0].name", "standard");
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].plans[0].description", "A simple plan");
catalogVariables.put("spring.cloud.openservicebroker.catalog.services[0].plans[0].free", "true");
return catalogVariables;
}
private Map<String, String> backingAppEnvironmentVariables(String[] backingAppProperties) {
Map<String, String> backingAppVariables = new HashMap<>();
for (String appProperty : backingAppProperties) {
final String[] appPropertyKeyValue = appProperty.split("=");
if (appPropertyKeyValue.length != 2) {
throw new RuntimeException(format("Backing app property '%s' is incorrectly formatted", Arrays.toString(appPropertyKeyValue)));
}
else backingAppVariables.put(appPropertyKeyValue[0], appPropertyKeyValue[1]);
}
return backingAppVariables;
}
public void createServiceInstance(String planName, String serviceName, String serviceInstanceName) {
cloudFoundryOperations
.services()
.createInstance(CreateServiceInstanceRequest
.builder()
.planName(planName)
.serviceName(serviceName)
.serviceInstanceName(serviceInstanceName)
.build())
.block();
}
private static SetEnvironmentVariableApplicationRequest createEnvRequest(String appName, String key, String value) {
return SetEnvironmentVariableApplicationRequest
.builder()
.name(appName)
.variableName(key)
.variableValue(value)
.build();
}
public List<ApplicationSummary> getApplications() {
return cloudFoundryOperations.applications().list().collectList().block();
}
public ApplicationEnvironments getApplicationEnvironmentByAppName(String appName) {
return cloudFoundryOperations
.applications()
.getEnvironments(GetApplicationEnvironmentsRequest.builder().name(appName).build())
.block();
}
public void setBrokerAppEnvironment(String[] properties) {
Flux<Void> catalogPublishers = getCatalogPublishers();
Flux<Void> appBrokerCFPublishers = getAppBrokerCFPublishers();
Flux<Void> appBrokerApplicationPublishers = Flux.concat(Arrays.stream(properties)
.filter(property -> property.contains("="))
.map(property -> property.split("="))
.map(property -> setEnvRequest(property[0], property[1]))
.collect(Collectors.toList()));
Flux.concat(catalogPublishers, appBrokerCFPublishers, appBrokerApplicationPublishers).blockLast();
}
private Flux<Void> getAppBrokerCFPublishers() {
return Flux.concat(
setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.api-host", cloudFoundryProperties.getApiHost()),
setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.api-port", String.valueOf(cloudFoundryProperties.getApiPort())),
setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.username", cloudFoundryProperties.getUsername()),
setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.password", cloudFoundryProperties.getPassword()),
setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.default-org", cloudFoundryProperties.getDefaultOrg()),
setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.default-space", cloudFoundryProperties.getDefaultSpace()),
setEnvRequest("spring.cloud.appbroker.deployer.cloudfoundry.skip-ssl-validation", String.valueOf(cloudFoundryProperties.isSkipSslValidation()))
);
}
private Flux<Void> getCatalogPublishers() {
return Flux.concat(
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].id", "example-service"),
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].name", "example"),
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].description", "A simple example"),
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].bindable", "true"),
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].tags[0]", "example"),
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].id", "simple-plan"),
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].bindable", "true"),
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].name", "standard"),
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].description", "A simple plan"),
setEnvRequest("spring.cloud.openservicebroker.catalog.services[0].plans[0].free", "true")
);
}
private Mono<Void> setEnvRequest(String key, String value) {
return cloudFoundryOperations
.applications()
.setEnvironmentVariable(createEnvRequest("sample-broker", key, value))
.doOnSuccess(v -> LOGGER.info("Environment with key {} set", key));
private <T> Mono<T> loggingMono(Mono<T> publisher) {
if (LOGGER.isDebugEnabled()) {
return publisher.log();
}
else return publisher;
}
}

View File

@@ -1,3 +1,4 @@
logging:
level:
cloudfoundry-client: DEBUG
cloudfoundry-client: ERROR
org.springframework.cloud.appbroker: DEBUG