Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add kubectl rollout option #1

Draft
wants to merge 9 commits into
base: main-apptweak
Choose a base branch
from
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine/helm:3.14.0
ROM alpine/helm:3.14.0
# Helm supported version along with K8 version: https://helm.sh/docs/topics/version_skew/

LABEL org.opencontainers.image.source=https://github.com/apptweak/concourse-helm3-resource
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ build:
docker build --platform linux/x86_64 --tag ${ECR_REPO}/${ID}:${TAG_VERSION} .

push:
# read --local --silent --prompt "Docker account's password: " passwd
# echo "$passwd" | docker login --username apptweakci --password-stdin
read --local --silent --prompt "Docker account's password: " gh_pat
echo "${gh_pat}"
# echo "${gh_pat}" | docker login "${ECR_REPO}" --username apptweakci --password-stdin
Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,22 @@ Deploy an helm chart
- `show_diff`: _Optional._ Show the diff that is applied if upgrading an existing successful release. (Default: false)
- `skip_missing_values:` _Optional._ Missing values files are skipped if they are specified in the values but do not exist. (Default false)

### `out`: Rollout deployment resources

Rollout a specific deployment either based a selector or component name either by changing the Deplyment's image.


- `rollout`: **Optional** To run a `kubectl rollout restart` (Default: false)

#### Parameters

- `rollout_resources`: **REQUIRED** List of resources parameters to restart (Default: [])
- `namespace`: _Optional_ (Default: "default")
- `component`: _Optional_ (Default: "")
- `selector`: _Optional_ (Default: "")
- `set_image`: _Optional_ (Default: "")
- `undo_on_fail`: _Optional_ Exec `kubectl rollout undo` if the status of the rollout ends up as a failure (Default: false)

## Example

### Out
Expand Down Expand Up @@ -281,3 +297,20 @@ jobs:
chart: oci://01234567890.dkr.ecr.us-west-2.amazonaws.com/myapp-helm-repo
# ...
```

Rollout `wordpress` components after pushing a new built-image to ECR

```yaml
jobs:
# ...
plan:
- put: myapp-helm
params:
rollout_resources:
- namespace: cms-wordpress
selector: app=wordpress
set_image: "{{docker-app/repository}}@{{docker-app/digest}}"
# ...
```


2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.35.0
1.35.0-apptweak-1
50 changes: 44 additions & 6 deletions assets/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ setup_kubernetes() {
echo "missing cluster_ca or cluster_ca_base64"
exit 1
fi

kubectl config set-cluster default --server=$cluster_url --certificate-authority=$ca_path $tls_server_name
fi

Expand Down Expand Up @@ -92,7 +92,7 @@ setup_aws_kubernetes() {

region=$(jq -r '.source.aws.region // ""' < $payload)
cluster_name=$(jq -r '.source.aws.cluster_name // ""' < $payload)

# only relevant to non-role based auth
# no default value in order to support instance profile
profile=$(jq -r '.source.aws.profile // ""' < $payload)
Expand Down Expand Up @@ -155,7 +155,7 @@ setup_aws_kubernetes() {
else
# defaults to use instance identity.
echo "no role or user specified. Fallback to use identity of the instance e.g. instance profile) to set up kubeconfig"

aws eks update-kubeconfig --region ${region} --name ${cluster_name} ${profile_opt}
fi
echo "done setting up kubeconfig for EKS"
Expand Down Expand Up @@ -189,14 +189,14 @@ setup_gcp_kubernetes() {
echo "$gcloud_service_account_key_file" >> /gcloud.json
gcloud_path="/gcloud.json"
fi

gcloud_service_account_name=($(cat $gcloud_path | jq -r ".client_email"))
gcloud auth activate-service-account ${gcloud_service_account_name} --key-file $gcloud_path
gcloud config set account ${gcloud_service_account_name}
else
echo "Workload Identity is enabled - no need to authenticate with a private key"
fi

gcloud config set project ${gcloud_project_name}
gcloud container clusters get-credentials ${gcloud_k8s_cluster_name} --zone ${gcloud_k8s_zone}

Expand All @@ -206,7 +206,6 @@ setup_helm() {
# $1 is the name of the payload file
# $2 is the name of the source directory


history_max=$(jq -r '.source.helm_history_max // "10"' < $1)

helm_bin="helm"
Expand Down Expand Up @@ -323,3 +322,42 @@ setup_doctl() {

doctl kubernetes cluster kubeconfig save $doctl_cluster_id
}

# Function to interpolate placeholders in a text string
interpolate() {
local text="$1" # The input text string
local source_dir="$2" # The directory where files referenced by input_text are located

# Replace placeholders with environment variable values
# This sed command looks for patterns like {{name}} and replaces them with the value of the $name environment variable
placeholder=$(echo "$text" | sed -E 's/\{\{(\$[A-Za-z_][A-Za-z0-9_]*)\}\}/\1/g')

# This sed command looks for patterns like {{filename}} and replaces them with the contents of the file specified by the placeholder
placeholder=$(echo "$placeholder" | sed -E 's/\{\{([A-Za-z_][A-Za-z0-9_/\-]*)\}\}/\1/g')
if [[ "$text" != "$placeholder" ]]; then
if echo "$placeholder" | grep -q "@"; then
image_repo=$(echo "$placeholder" | cut -d'@' -f1)
image_repo=$(get_file_contents "$source_dir/$image_repo")
image_digest=$(echo "$placeholder" | cut -d'@' -f2)
image_digest=$(get_file_contents "$source_dir/$image_digest")
placeholder="${image_repo}@${image_digest}"
else
placeholder=$(get_file_contents "$source_dir/$placeholder")
fi
fi
# Replace placeholders with file contents

echo "$placeholder" # Output the processed text
}

# Function to read the contents of a file
get_file_contents() {
local path="$1" # The path to the file to read
if [ -f "$path" ]; then
cat "$path" # Output the contents of the file
else
echo "Error: File '$path' not found" >&2 # Print an error message to stderr if the file does not exist
exit 1 # Exit the script with an error status
fi
}

84 changes: 78 additions & 6 deletions assets/out
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ cat > $payload <&0

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

private_registry=$(jq -r '.params|has("private_registry")' < $payload)
Expand All @@ -30,6 +30,7 @@ release_overwrite=$(jq -r '.params.release // ""' < $payload)
values=$(jq -r '.params.values // "" | if type == "array" then .[] else . end' < $payload)
debug=$(jq -r '.params.debug // "false"' < $payload)
replace=$(jq -r '.params.replace // "false"' < $payload)
rollout=$(jq -r '.params.rollout // "false"' < $payload)
uninstall=$(jq -r '.params.uninstall // "false"' < $payload)
delete_namespace=$(jq -r '.params.delete_namespace // "false"' < $payload)
test=$(jq -r '.params.test // "false"' < $payload)
Expand All @@ -46,7 +47,7 @@ wait_for_jobs=$(jq -r '.params.wait_for_jobs // "false"' < $payload)
timeout=$(jq -r '.params.timeout // "5m0s"' < $payload)
skip_missing_values=$(jq -r '.params.skip_missing_values // "false"' < $payload)

if [[ "$test" == "false" ]] && [[ "$uninstall" == "false" ]] && [ -z "$chart" ] ; then
if [[ "$test" == "false" ]] && [[ "$uninstall" == "false" ]] && [ -z "$chart" ] && [[ "$rollout" == "false" ]]; then
echo "invalid payload (missing chart)"
exit 1
fi
Expand All @@ -66,6 +67,11 @@ if [[ "$uninstall" == "true" ]] && [[ "$replace" == "true" ]] ; then
exit 1
fi

if [[ "$uninstall" == "true" ]] && [[ "$rollout" == "true" ]] ; then
echo "invalid payload ( uninstall and rollout cannot both be true )"
exit 1
fi

if [ -f "$source/$namespace_overwrite" ]; then
namespace=$(cat $source/$namespace_overwrite)
elif [ -n "$namespace_overwrite" ]; then
Expand Down Expand Up @@ -229,7 +235,7 @@ helm_upgrade() {
upgrade_args+=("--wait")
upgrade_args+=("--wait-for-jobs")
elif [ "$check_is_ready" == "true" ]; then
upgrade_args+=("--wait")
upgrade_args+=("--wait")
fi

if [ "$atomic" == "true" ]; then
Expand Down Expand Up @@ -340,13 +346,13 @@ helm_test() {
echo "invalid payload (missing release if test=true)"
exit 1
fi

if [ "$debug" = true ]; then
test_args+=("--debug")
fi

test_args+=("--namespace=$namespace" "test" "$release" "--timeout=$timeout")

if [ "$test_logs" = true ] ; then
test_args+=("--logs")
fi
Expand All @@ -358,6 +364,69 @@ helm_test() {
$helm_bin ${test_args[@]} | tee $logfile
}

kubectl_rollout() {
rollout_resources=$(jq -c '(try .params.rollout_resources[]? catch [][])' < $payload)

if [[ $rollout_resources ]]
then
logfile="/tmp/log"
mkdir -p /tmp

for res in $rollout_resources; do
extra_args=""
selector=$(echo $res | jq -cr '.selector // ""')
namespace=$(echo $res | jq -cr '.namespace // "default"')
component=$(echo $res | jq -cr '.component // ""')
set_image=$(echo $res | jq -cr '.set_image // ""')
undo_on_fail=$(echo $res | jq -cr '.undo_on_fail // "false"')

if [[ -z $selector && -z $component ]]; then
echo "invalid params for kubectl_rollout operation, please pass either a selector or namespace & component"
continue
fi

if [[ -z $set_image && -z $selector ]]; then
echo "invalid params for kubectl_set_image operation, please pass either selector param. along the set_image param."
continue
fi

if [[ ! -z $set_image && ! -z $component ]]; then
echo "invalid params for kubectl operation, please pass either component or set_image"
continue
fi

if [[ ! -z $set_image ]]; then
set_image=$(interpolate "$set_image" "$source")
fi

if [[ ! -z $component ]]; then
extra_args+=" $component "
fi

if [[ ! -z $selector ]]; then
extra_args+=" --selector=$selector "
fi

echo "Restarting namespace: ${namespace} || component: ${component} || set_image: ${set_image} || extra_args: ${extra_args}"

if [[ ! -z $set_image ]]; then
kubectl --namespace $namespace set image deployment "*=$set_image" $extra_args | tee "$logfile"
else
kubectl --namespace $namespace rollout restart deployment $extra_args | tee "$logfile"
fi

kubectl --namespace $namespace rollout status deployment $extra_args --timeout "$timeout" | tee "$logfile"
wait_status=$?

if [[ "$undo_on_fail" = "true" && $wait_status != 0 ]]; then
kubectl --namespace $namespace rollout undo deployment $extra_args | tee "$logfile"
exit $wait_status
fi
done
fi
}


# support private registry
if [ "${private_registry}" = true ]; then
echo "private registry configured. proceeding with helm registry login"
Expand All @@ -384,6 +453,9 @@ elif [ "$replace" = true ]; then
helm_upgrade
result="$(jq -n "{version:{release:\"$release\", tested: \"true\"}, metadata: [{name: \"release\", value: \"$release\"}]}")"
echo "$result" | jq -s add >&3
elif [ "$rollout" = true ]; then
kubectl_rollout
echo '{"version": {"revision": "N/A", "release": "N/A"}}' >&3
else
echo "Installing $release"
helm_upgrade
Expand Down