Skip to content
This repository has been archived by the owner on Aug 28, 2021. It is now read-only.

Commit

Permalink
feat: Enable pool kubeconfig pattern (#117)
Browse files Browse the repository at this point in the history
Allow the concourse-helm-resource to be used in put-only mode with a
kubeconfig file.

This builds off of pr46, but extends it so that there is no need to
configure the resource with the cluster_url (or anything else specified
in the kubeconfig).

Additionally, add a number of additional related controls:

- Get the k8s namespace from the kubeconfig file
- Ability to configure the tiller namespace
- Ability to enable tracing for debugging the resource
- Expose the helm init --wait option
- Ability to specify the kubeconfig path on put.

Together, these features allow us to use the helm resource in conjunction
with the pool resource and the kubernetes resource to:

- Dynamically pick a k8s namespace from a pool, granting exclusive access
- Run a helm-based standalone integration test against a clean environment
  in a dedicated namespace with a dedicated tiller
  - The concourse-helm-resource cleans up anything that may have been in the
    namespace's tiller
  - The concourse-helm-resource installs helm into the namespace
  - The concourse-helm-resource waits for tiller to be ready
  - The concourse-helm-resource installs the chart into the namespace with
    the namespace-specific tiller
- Gather the results (with the kubernetes resource)
- Return the k8s namespace to the pool
  • Loading branch information
hstenzel authored and msiegenthaler committed May 13, 2019
1 parent 015f10b commit a62f5de
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 49 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ resource_types:
## Source Configuration
* `cluster_url`: *Required.* URL to Kubernetes Master API service
* `cluster_url`: *Optional.* URL to Kubernetes Master API service. Do not set when using the `kubeconfig_path` parameter, otherwise required.
* `cluster_ca`: *Optional.* Base64 encoded PEM. Required if `cluster_url` is https.
* `token`: *Optional.* Bearer token for Kubernetes. This, 'token_path' or `admin_key`/`admin_cert` are required if `cluster_url` is https.
* `token_path`: *Optional.* Path to file containing the bearer token for Kubernetes. This, 'token' or `admin_key`/`admin_cert` are required if `cluster_url` is https.
Expand All @@ -39,12 +39,17 @@ resource_types:
* `repos`: *Optional.* Array of Helm repositories to initialize, each repository is defined as an object with properties `name`, `url` (required) username and password (optional).
* `plugins`: *Optional.* Array of Helm plugins to install, each defined as an object with properties `url` (required), `version` (optional).
* `stable_repo`: *Optional* Override default Helm stable repo <https://kubernetes-charts.storage.googleapis.com>. Useful if running helm deploys without internet access.
* `kubeconfig_namespace`: *Optional.* Use the kubeconfig context namespace as the helm namespace. (Default: false)
* `kubeconfig_tiller_namespace`: *Optional.* Use the kubeconfig context namespace as the tiller namespace. (Default: false)
* `tracing_enabled`: *Optional.* Enable extremely verbose tracing for this resource. Useful when developing the resource itself. May allow secrets to be displayed. (Default: false)
* `helm_init_wait`: *Optional.* When initializing the helm server, use the `--wait` option. (Default: false)
* `helm_setup_purge_all`: *Optional.* Delete and purge every helm release. Use with extreme caution. (Default: false)

## Behavior

### `check`: Check for new releases

Any new revisions to the release are returned, no matter their current state. The release must be specified in the
Any new revisions to the release are returned, no matter their current state. The release and cluster url must be specified in the
source for `check` to work.

### `in`: Not Supported
Expand Down Expand Up @@ -83,6 +88,7 @@ on the cluster.
* `exit_after_diff`: *Optional.* Show the diff but don't actually install/upgrade. (Default: false)
* `reuse_values`: *Optional.* When upgrading, reuse the last release's values. (Default: false)
* `wait`: *Optional.* Allows deploy task to sleep for X seconds before continuing to next task. Allows pods to restart and become stable, useful where dependency between pods exists. (Default: 0)
* `kubeconfig_path`: *Optional.* File containing a kubeconfig. Overrides source configuration for cluster, token, and admin config.

## Example

Expand Down
26 changes: 22 additions & 4 deletions assets/check
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ source /opt/resource/common.sh
payload=$(mktemp $TMPDIR/helm-resource-request.XXXXXX)
cat > $payload <&0

# Prepare
setup_resource $payload
echo "Resource setup successful."

# Parse parameters
namespace=$(jq -r '.source.namespace // "default"' < $payload)
release=$(jq -r '.source.release // ""' < $payload)
tiller_namespace=$(jq -r '.source.tiller_namespace // "kube-system"' < $payload)
cluster_url=$(jq -r '.source.cluster_url // ""' < $payload)

tillerless=$(jq -r '.source.tillerless // "false"' < $payload)
if [ "$tillerless" = true ]; then
Expand All @@ -40,6 +37,27 @@ fi

current_rev=$(jq -r '.version.revision // "0"' < $payload || true)

if [ -z "$cluster_url" ]; then
# Since the resource was configured to use a dynamic namespace,
# check does not make any sense since it is impossible to know which
# tiller and which release name should be checked.
# The resource is in "put" only mode.
if [ $current_rev -eq 0 ]; then
# When there was no `version` passed in, return an empty list
echo '[]' >&3
else
# When the `version` was passed in, parrot it back
printf '[{"revision":"%s","release":"%s"}]\n' "$current_rev" "$release" | jq -M . >&3
fi

# Check has successfully finished so exit with success.
exit 0
fi

# Prepare
setup_resource $payload
echo "Resource setup successful."

if [ "$current_rev" -eq "0" ]; then
# Empty => return the current
$helm_bin history $tls_flag --tiller-namespace $tiller_namespace --max 20 $release | tail -n 1 | while read -r line; do
Expand Down
116 changes: 78 additions & 38 deletions assets/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,48 @@ set -e
setup_kubernetes() {
payload=$1
source=$2
# Setup kubectl
cluster_url=$(jq -r '.source.cluster_url // ""' < $payload)
if [ -z "$cluster_url" ]; then
echo "invalid payload (missing cluster_url)"
exit 1
fi
if [[ "$cluster_url" =~ https.* ]]; then

cluster_ca=$(jq -r '.source.cluster_ca // ""' < $payload)
admin_key=$(jq -r '.source.admin_key // ""' < $payload)
admin_cert=$(jq -r '.source.admin_cert // ""' < $payload)
token=$(jq -r '.source.token // ""' < $payload)
token_path=$(jq -r '.params.token_path // ""' < $payload)

kubeconfig_path=$(jq -r '.params.kubeconfig_path // ""' < $payload)
absolute_kubeconfig_path="${source}/${kubeconfig_path}"
if [ -f "$absolute_kubeconfig_path" ]; then
mkdir -p /root/.kube
cp "$absolute_kubeconfig_path" "/root/.kube/config"
else
# Setup kubectl
cluster_url=$(jq -r '.source.cluster_url // ""' < $payload)
if [ -z "$cluster_url" ]; then
echo "invalid payload (missing cluster_url)"
exit 1
fi
if [[ "$cluster_url" =~ https.* ]]; then

cluster_ca=$(jq -r '.source.cluster_ca // ""' < $payload)
admin_key=$(jq -r '.source.admin_key // ""' < $payload)
admin_cert=$(jq -r '.source.admin_cert // ""' < $payload)
token=$(jq -r '.source.token // ""' < $payload)
token_path=$(jq -r '.params.token_path // ""' < $payload)

if [ -f "$source/$token_path" ]; then
kubectl config set-credentials admin --token=$(cat $source/$token_path)
elif [ ! -z "$token" ]; then
kubectl config set-credentials admin --token=$token
else
key_path="/root/.kube/key.pem"
cert_path="/root/.kube/cert.pem"
echo "$admin_key" | base64 -d > $key_path
echo "$admin_cert" | base64 -d > $cert_path
kubectl config set-credentials admin --client-certificate=$cert_path --client-key=$key_path
fi

ca_path="/root/.kube/ca.pem"
echo "$cluster_ca" | base64 -d > $ca_path
kubectl config set-cluster default --server=$cluster_url --certificate-authority=$ca_path

if [ -f "$source/$token_path" ]; then
kubectl config set-credentials admin --token=$(cat $source/$token_path)
elif [ ! -z "$token" ]; then
kubectl config set-credentials admin --token=$token
kubectl config set-context default --cluster=default --user=admin
else
key_path="/root/.kube/key.pem"
cert_path="/root/.kube/cert.pem"
echo "$admin_key" | base64 -d > $key_path
echo "$admin_cert" | base64 -d > $cert_path
kubectl config set-credentials admin --client-certificate=$cert_path --client-key=$key_path
kubectl config set-cluster default --server=$cluster_url
kubectl config set-context default --cluster=default
fi

kubectl config set-context default --cluster=default --user=admin
else
kubectl config set-cluster default --server=$cluster_url
kubectl config set-context default --cluster=default
kubectl config use-context default
fi

kubectl config use-context default
kubectl version
}

Expand Down Expand Up @@ -81,7 +84,15 @@ setup_tls() {

setup_helm() {
init_server=$(jq -r '.source.helm_init_server // "false"' < $1)
tiller_namespace=$(jq -r '.source.tiller_namespace // "kube-system"' < $1)

kubeconfig_tiller_namespace=$(jq -r '.source.kubeconfig_tiller_namespace // "false"' <$1)
if [ "$kubeconfig_tiller_namespace" = "true" ]
then
tiller_namespace=$(kubectl config view --minify -ojson | jq -r .contexts[].context.namespace)
else
tiller_namespace=$(jq -r '.source.tiller_namespace // "kube-system"' < $1)
fi

tillerless=$(jq -r '.source.tillerless // "false"' < $payload)
tls_enabled=$(jq -r '.source.tls_enabled // "false"' < $payload)
history_max=$(jq -r '.source.helm_history_max // "0"' < $1)
Expand All @@ -105,6 +116,13 @@ setup_helm() {
exit 1
fi
tiller_service_account=$(jq -r '.source.tiller_service_account // "default"' < $1)

helm_init_wait=$(jq -r '.source.helm_init_wait // "false"' <$1)
helm_init_wait_arg=""
if [ "$helm_init_wait" = "true" ]; then
helm_init_wait_arg="--wait"
fi

if [ "$tls_enabled" = true ]; then
tiller_key=$(jq -r '.source.tiller_key // ""' < $payload)
tiller_cert=$(jq -r '.source.tiller_cert // ""' < $payload)
Expand All @@ -121,19 +139,29 @@ setup_helm() {
helm_ca_cert_path="/root/.helm/ca.pem"
echo "$tiller_key" > $tiller_key_path
echo "$tiller_cert" > $tiller_cert_path
$helm_bin init --tiller-tls --tiller-tls-cert $tiller_cert_path --tiller-tls-key $tiller_key_path --tiller-tls-verify --tls-ca-cert $tiller_key_path --tiller-namespace=$tiller_namespace --service-account=$tiller_service_account --history-max=$history_max $stable_repo --upgrade
$helm_bin init --tiller-tls --tiller-tls-cert $tiller_cert_path --tiller-tls-key $tiller_key_path --tiller-tls-verify --tls-ca-cert $tiller_key_path --tiller-namespace=$tiller_namespace --service-account=$tiller_service_account --history-max=$history_max $stable_repo --upgrade $helm_init_wait_arg
else
$helm_bin init --tiller-namespace=$tiller_namespace --service-account=$tiller_service_account --history-max=$history_max $stable_repo --upgrade
$helm_bin init --tiller-namespace=$tiller_namespace --service-account=$tiller_service_account --history-max=$history_max $stable_repo --upgrade $helm_init_wait_arg
fi
wait_for_service_up tiller-deploy 10
else
export HELM_HOST=$(jq -r '.source.helm_host // ""' < $1)
$helm_bin init -c --tiller-namespace $tiller_namespace $stable_repo > /dev/null
fi

tls_enabled_arg=""
if [ "$tls_enabled" = true ]; then
$helm_bin version --tls --tiller-namespace $tiller_namespace
else
$helm_bin version --tiller-namespace $tiller_namespace
tls_enabled_arg="--tls"
fi
$helm_bin version $tls_enabled_arg --tiller-namespace $tiller_namespace

helm_setup_purge_all=$(jq -r '.source.helm_setup_purge_all // "false"' <$1)
if [ "$helm_setup_purge_all" = "true" ]; then
local release
for release in $(helm ls -aq --tiller-namespace $tiller_namespace )
do
helm delete $tls_enabled_arg --purge "$release" --tiller-namespace $tiller_namespace
done
fi
}

Expand All @@ -154,7 +182,14 @@ wait_for_service_up() {
setup_repos() {
repos=$(jq -c '(try .source.repos[] catch [][])' < $1)
plugins=$(jq -c '(try .source.plugins[] catch [][])' < $1)
tiller_namespace=$(jq -r '.source.tiller_namespace // "kube-system"' < $1)

kubeconfig_tiller_namespace=$(jq -r '.source.kubeconfig_tiller_namespace // "false"' <$1)
if [ "$kubeconfig_tiller_namespace" = "true" ]
then
tiller_namespace=$(kubectl config view --minify -ojson | jq -r .contexts[].context.namespace)
else
tiller_namespace=$(jq -r '.source.tiller_namespace // "kube-system"' < $1)
fi

local IFS=$'\n'

Expand Down Expand Up @@ -185,6 +220,11 @@ setup_repos() {
}

setup_resource() {
tracing_enabled=$(jq -r '.source.tracing_enabled // "false"' < $1)
if [ "$tracing_enabled" = "true" ]; then
set -x
fi

echo "Initializing kubectl..."
setup_kubernetes $1 $2
echo "Initializing helm..."
Expand Down
24 changes: 19 additions & 5 deletions assets/out
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ echo "Resource setup successful."
# Parse parameters
tillerless=$(jq -r '.source.tillerless // "false"' < $payload)
namespace=$(jq -r '.source.namespace // "default"' < $payload)
tiller_namespace=$(jq -r '.source.tiller_namespace // "kube-system"' < $payload)
chart=$(jq -r '.params.chart // ""' < $payload)
version=$(jq -r '.params.version // ""' < $payload)
namespace_file=$(jq -r '.params.namespace // ""' < $payload)
Expand All @@ -42,6 +41,7 @@ exit_after_diff=$(jq -r '.params.exit_after_diff // "false"' < $payload)
reuse_values=$(jq -r '.params.reuse_values // "false"' < $payload)
wait=$(jq -r '.params.wait // 0' < $payload)
check_is_ready=$(jq -r '.params.check_is_ready // "false"' < $payload)
kubeconfig_namespace=$(jq -r '.source.kubeconfig_namespace // "false"' < $payload)

if [ -z "$chart" ]; then
if [[ "$test" == "false" && "$delete" == "false" ]]; then
Expand All @@ -56,6 +56,10 @@ elif [ -n "$namespace_file" ]; then
namespace=$namespace_file
fi

if [ "$kubeconfig_namespace" = "true" ] ; then
namespace=$(kubectl config view --minify -ojson | jq -r .contexts[].context.namespace)
fi

if [ -n "$release_file" ]; then
if [ -f "$source/$release_file" ]; then
release=`cat $source/$release_file`
Expand Down Expand Up @@ -108,12 +112,19 @@ set_overridden_values() {

# Find the current revision of a helm release
current_deployed() {
local release="$1"
$helm_bin history $tls_flag --tiller-namespace $tiller_namespace --max 20 $release | grep "DEPLOYED"
}

helm_upgrade() {
upgrade_args=("upgrade" "$release" $chart_full "--tiller-namespace=$tiller_namespace")
non_diff_args=("--install" "--namespace" "$namespace")
non_diff_args=("--namespace" "$namespace")
if [ "$release" = "" ]; then
upgrade_args=("install" $chart_full "--tiller-namespace=$tiller_namespace")
else
upgrade_args=("upgrade" "$release" $chart_full "--tiller-namespace=$tiller_namespace")
non_diff_args+=("--install")
fi

if [ -n "$values" ]; then
for value in $values; do
upgrade_args+=("-f" "$source/"$value)
Expand Down Expand Up @@ -160,7 +171,7 @@ helm_upgrade() {
helm_args=("${upgrade_args[@]}" "${overridden_args[@]}" "${non_diff_args[@]}")
helm_echo_args=("${upgrade_args[@]}" "${scrubbed_overridden_args[@]}" "${non_diff_args[@]}")
helm_diff_args=("${upgrade_args[@]}" "${overridden_args[@]}" "--suppress-secrets" "--allow-unreleased")
if [ "$show_diff" = true ] && current_deployed > /dev/null && [ "$devel" != true ]; then
if [ "$show_diff" = true ] && current_deployed "$release"> /dev/null && [ "$devel" != true ]; then
if [ "$tls_enabled" = true ]; then
echo "helm diff does not support TLS at the present moment."
else
Expand Down Expand Up @@ -265,7 +276,10 @@ else
echo "Installing $release"
helm_upgrade

deployed=$(current_deployed)
if [ "$release" = "" ]; then
release=$(helm ls -qrd --tiller-namespace $tiller_namespace --max 20 | head -1)
fi
deployed=$(current_deployed "$release")
revision=$(echo $deployed | awk '{ print $1 }')
chart=$(echo $deployed | awk '{ print $8 }')
echo "Deployed revision $revision of $release"
Expand Down

0 comments on commit a62f5de

Please sign in to comment.