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

ROX-8830: Respect local overwrites of variables in BASH_ENV #99

Merged
merged 27 commits into from
Jan 6, 2022
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
42fc5fc
X-Smart-Branch-Parent: master
vikin91 Jan 4, 2022
11d5582
X-Smart-Squash: Squashed 25 commits:
vikin91 Dec 27, 2021
ed0da3e
Debug: Enable debug in cci-export
vikin91 Dec 28, 2021
c30a448
Fix further issues in cci-export. Add regression tests for them
vikin91 Dec 28, 2021
f3c0940
Support case when BASH_ENV is not a file
vikin91 Dec 28, 2021
c5264af
Improve bash-wrapper
vikin91 Jan 3, 2022
1e67044
Rename Dockefiles
vikin91 Jan 3, 2022
c683bd5
Minor: Rephrase comment
vikin91 Jan 3, 2022
2439030
Fix issue when BASH_ENV is not a file + refactor
vikin91 Jan 4, 2022
9166ca5
Add test case for potentially colliding variable names
vikin91 Jan 4, 2022
bf07491
Add test case for esacping special characters
vikin91 Jan 4, 2022
6939ece
Quote value to fix failing test for esacping special chars
vikin91 Jan 4, 2022
80c0cf7
Rephrase comment
vikin91 Jan 5, 2022
a6bd62f
Rephrase test name
vikin91 Jan 5, 2022
2eefb26
Add test-case with 'export FOO'
vikin91 Jan 5, 2022
f4be15b
Test: Add more special chars to the escaping test case
vikin91 Jan 5, 2022
a3a9f01
Remove optional 'run cat '
vikin91 Jan 5, 2022
21ecfc9
Rename 'cci-export-test' to 'test-cci-export'
vikin91 Jan 5, 2022
e2267a5
Run bats tests on custom executor with 'setup_remote_docker' in a sin…
vikin91 Jan 5, 2022
0e294e3
Fix: use default executor
vikin91 Jan 5, 2022
4940119
Docs: Improve comments in bash-wrapper
vikin91 Jan 5, 2022
baff548
Remove 'git reset --hard origin/HEAD'
vikin91 Jan 5, 2022
34179a4
Add testcase for secaping $ sign
vikin91 Jan 5, 2022
f059562
Add setup assertions on the CIRCLECI variable
vikin91 Jan 5, 2022
c21a9f9
Simplify image name of cci-export tester container
vikin91 Jan 5, 2022
ec744d8
Rename 'run-test-cci-export' to 'test-cci-export'
vikin91 Jan 5, 2022
58a71b0
Create the FILE inside bats setup
vikin91 Jan 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
<<: *defaults
steps:
- build-and-push-image:
dockerfile-path: images/Dockerfile.rox
dockerfile-path: images/rox.Dockerfile

create-or-update-dependent-rox-pr:
<<: *defaults
Expand Down Expand Up @@ -80,24 +80,43 @@ jobs:
git config user.name "RoxBot"

branch_name="roxbot/update-ci-image-from-${CIRCLE_PULL_REQUEST##*/}"
git checkout "${branch_name}" || git checkout -b "${branch_name}"
git reset --hard origin/HEAD
if git fetch --quiet origin "${branch_name}"; then
git checkout "${branch_name}"
git pull --quiet --set-upstream origin "${branch_name}"
else
git checkout -b "${branch_name}"
git push --set-upstream origin "${branch_name}"
fi

sed -i '[email protected]/rhacs-eng/apollo-ci:.*@'"${pushed_image}"' # TODO(do not merge): After upstream PR is merged, cut a tag and update this@g' .circleci/config.yml
git commit -am "Bump base image to ${pushed_image##:}"
git push origin "${branch_name}" --force
git push origin "${branch_name}"
msugakov marked this conversation as resolved.
Show resolved Hide resolved

- run:
name: Create PR
command: |
branch_name="roxbot/update-ci-image-from-${CIRCLE_PULL_REQUEST##*/}"
.circleci/create_update_pr_rox.sh "${branch_name}"

run-test-cci-export:
<<: *defaults
resource_class: medium
steps:
- checkout
- setup_remote_docker
- run:
name: Test cci-export inside Docker
command: |
export TAG="$(git describe --tags --abbrev=10)"
docker build images/ -f images/test.cci-export.Dockerfile \
-t "quay.io/rhacs-eng/apollo-ci:test-cci-export-$TAG"
docker run "quay.io/rhacs-eng/apollo-ci:test-cci-export-${TAG}"
vikin91 marked this conversation as resolved.
Show resolved Hide resolved

build-and-push-env-check:
<<: *defaults
steps:
- build-and-push-image:
dockerfile-path: images/Dockerfile.env-check
dockerfile-path: images/env-check.Dockerfile
tag-prefix: "env-check-"

unit-test-env-check:
Expand Down Expand Up @@ -171,25 +190,31 @@ jobs:
<<: *defaults
steps:
- build-and-push-image:
dockerfile-path: images/Dockerfile.collector
dockerfile-path: images/collector.Dockerfile
tag-prefix: "collector-"

build-and-push-jenkins-plugin:
<<: *defaults
steps:
- build-and-push-image:
dockerfile-path: images/Dockerfile.jenkins-plugin
dockerfile-path: images/jenkins-plugin.Dockerfile
tag-prefix: "jenkins-plugin-"

workflows:
version: 2
build:
jobs:
- run-test-cci-export:
filters:
tags:
only: /.*/
- build-and-push-rox:
context: quay-rhacs-eng-readwrite
filters:
tags:
only: /.*/
requires:
- run-test-cci-export
- create-or-update-dependent-rox-pr:
filters:
branches:
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
47 changes: 30 additions & 17 deletions images/static-contents/bin/bash-wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,36 @@
# cci-export is a function which can be used to export environment variables in a way that is persistent
# across CircleCI steps.
cci-export() {
if [ "$#" -ne 2 ]; then
echo >&2 "Usage: $0 KEY VALUE"
return 1
fi

key="$1"
value="$2"

export "${key}=${value}"

if [[ "$CIRCLECI" == "true" ]]; then
if [[ -z "${BASH_ENV}" ]]; then
echo >&2 "Env var BASH_ENV not properly set"
return 1
fi
echo "export ${key}=$(printf '%q' "$value")" >> "$BASH_ENV"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my limited understanding of the issue, the problem is that BASH_ENV contents may override whatever is set in the environment before bash process is launched.

  1. Parent process exports environment variables.
  2. Parent process starts real bash (through our wrappers but that doesn't matter).
  3. bash sees BASH_ENV and sources that file.
  4. BASH_ENV overwrites environment variables if their names match the ones set by the parent process.

If we assume, we don't want overwriting only for values that we put into $BASH_ENV with our cci-export function, could we simply modify this specific line from

echo "export ${key}=$(printf '%q' "$value")" >> "$BASH_ENV"

to

echo "export ${key}=\${${key}:-$(printf '%q' "$value")}" >> "$BASH_ENV"

Warning: this needs testing if escaping works as it should.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, I added this in line 39.

This solution requires handling one edge-case: cci-exporting the same key twice with different values. In order to make this work properly, I needed to add line 35: remove_lines_starting_with "$BASH_ENV" "$(printf 'export %q="${%q:-' "$key" "$key")". There are unit tests in this PR to cover this edge-case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I did not even think about it. Let me take another look at the current approach.

fi
if [ "$#" -ne 2 ]; then
echo >&2 "Usage: $0 KEY VALUE"
return 1
fi

key="$1"
value="$2"

export "${key}=${value}"

if [[ "$CIRCLECI" == "true" ]]; then
if [[ -z "${BASH_ENV}" ]]; then
echo >&2 "Env var BASH_ENV not properly set"
return 1
fi

# Use export with default value: 'export FOO="${FOO:-bar}"', so that variables set in the environment are not overwritten by `$BASH_ENV`
key_part="export ${key}"
# shellcheck disable=SC2016 # we must produce literal ${} symbols
value_part="$(printf '"${%q:-"%q"}"\n' "$key" "$value")"

# Remove all export-lines for the same exported variable, to 'forget' about past cci-export calls,
# otherwise the first call to cci-export would define a default value for the variable, so that
# second and subsequent calls to cci-export would have no effect.
if [[ -f "$BASH_ENV" ]]; then
filtered_envfile="$(mktemp -t "bash.env-XXXX")"
grep --invert-match --fixed-strings "${key_part}=" "$BASH_ENV" > "${filtered_envfile}" && mv "${filtered_envfile}" "$BASH_ENV"
fi
echo "${key_part}=${value_part}" >> "$BASH_ENV"
fi
}

export -f cci-export
Expand Down
63 changes: 63 additions & 0 deletions images/test.cci-export.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
FROM ubuntu:20.04 as base
ARG DEBIAN_FRONTEND=noninteractive
SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# Configure all necessary apt repositories
RUN set -ex \
&& apt-get update \
&& apt-get install --no-install-recommends -y \
apt-transport-https \
ca-certificates \
gnupg2 \
wget \
git \
sudo \
nodejs \
&& wget --no-verbose -O - https://deb.nodesource.com/setup_lts.x | bash - \
&& wget --no-verbose -O - https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
&& apt-get remove -y \
apt-transport-https \
gnupg2 \
wget \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*

RUN set -ex \
&& apt-get update \
&& apt-get install --no-install-recommends -y \
nodejs \
&& rm -rf /var/lib/apt/lists/*

RUN set -ex \
&& npm install -g [email protected] [email protected] [email protected] \
&& bats -v

RUN set -ex \
&& groupadd --gid 3434 circleci \
&& useradd --uid 3434 --gid circleci --shell /bin/bash --create-home circleci \
&& echo 'circleci ALL=NOPASSWD: ALL' > /etc/sudoers.d/50-circleci

# Function-under-test setup
FROM base as image_under_test

COPY ./static-contents/ /static-tmp
RUN set -e \
&& for file in $(find /static-tmp -type f); do \
dir="$(dirname "${file}")"; new_dir="${dir#/static-tmp}"; mkdir -p "${new_dir}"; cp "${file}" "${new_dir}"; \
done \
&& rm -r /static-tmp

RUN \
mv /bin/bash /bin/real-bash && \
mv /bin/bash-wrapper /bin/bash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. The part above resembles things that happen in rox.Dockerfile. Have you considered using that image as some of the base stages?
Advantages should be:

  1. We don't need to maintain double installation procedures and maintain versions of dependencies in sync (e.g. bats&co) in two places.
  2. The test would verify the actual rox ci image and not "something somewhat similar to it".

It may be a bit annoying that apollo-ci image is pushed before testing but that happens before publishing rox PR, so shouldn't be that bad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea but it feels like a lot of changes - I will give it a try

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will try in a separate branch pr/ROX-8830-common-container-base to separate this thread from the rest

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Let's do it as a follow-up PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I branched off this branch and almost got it - see #101


# Test setup
FROM image_under_test as tester

USER circleci
WORKDIR /home/circleci/test
COPY --chown=circleci:circleci test/ .
ENV CIRCLECI=true

CMD ["bats", "--print-output-on-failure", "--verbose-run", "/home/circleci/test/bats/"]
1 change: 1 addition & 0 deletions images/test/bats/FILE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.2.3
msugakov marked this conversation as resolved.
Show resolved Hide resolved
140 changes: 140 additions & 0 deletions images/test/bats/cci-export.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/usr/bin/env bats

# To run the test locally do:
# docker build -t apollo-cci:test -f images/test.cci-export.Dockerfile images && docker run -it apollo-cci:test

bats_helpers_root="/usr/lib/node_modules"
load "${bats_helpers_root}/bats-support/load.bash"
load "${bats_helpers_root}/bats-assert/load.bash"

setup() {
msugakov marked this conversation as resolved.
Show resolved Hide resolved
bash_env="$(mktemp)"
export BASH_ENV="$bash_env"
# ensure clean start of every test case
unset FOO
echo "" > "$bash_env"
run echo $BASH_ENV
assert_output "$bash_env"
run cat $BASH_ENV
assert_output ""
run "$HOME/test/foo-printer.sh"
assert_output "FOO: "
}

@test "cci-export BASH_ENV does not exist" {
run rm -f "${BASH_ENV}"
run test -f "${BASH_ENV}"
assert_failure

run cci-export FOO cci1
assert_success
run "$HOME/test/foo-printer.sh"
assert_output "FOO: cci1"
refute_output "FOO: "
}

@test "cci-export sanity check single value" {
run cci-export FOO cci1
assert_success
run "$HOME/test/foo-printer.sh"
assert_output "FOO: cci1"
refute_output "FOO: "

run cci-export FOO cci2
assert_success
run "$HOME/test/foo-printer.sh"
assert_output "FOO: cci2"
refute_output "FOO: cci1"
}

@test "cci-export should escape special characters in values" {
run cci-export FOO 'quay.io/rhacs-"eng"/super scanner:2.21.0-15-{{g44}(8f)2dc8fa}'
assert_success
run "$HOME/test/foo-printer.sh"
assert_output 'FOO: quay.io/rhacs-"eng"/super scanner:2.21.0-15-{{g44}(8f)2dc8fa}'
refute_output "FOO: "
}

@test "cci-export sanity check many values" {
export _FILE="$HOME/test/bats/FILE"
run cat "${_FILE}"
assert_output "1.2.3"

export VAR=placeholder
run cci-export VAR1 "text/$VAR/text:$(cat "${_FILE}")"
run cci-export VAR2 "text/$VAR/text:$(cat "${_FILE}")"
run cci-export IMAGE3 "text/$VAR/text:$(cat "${_FILE}")"

run "$HOME/test/foo-printer.sh" "VAR1"
assert_output "VAR1: text/$VAR/text:$(cat "${_FILE}")"
assert_output "VAR1: text/placeholder/text:1.2.3"

run "$HOME/test/foo-printer.sh" VAR2
assert_output "VAR2: text/$VAR/text:$(cat "${_FILE}")"
assert_output "VAR2: text/placeholder/text:1.2.3"

run "$HOME/test/foo-printer.sh" IMAGE3
assert_output "IMAGE3: text/$VAR/text:$(cat "${_FILE}")"
assert_output "IMAGE3: text/placeholder/text:1.2.3"
}

@test "cci-export potentially colliding variable names" {
run cci-export PART1 "value1"
run cci-export PART1_PART2 "value_joined"
run cci-export PART1 "value2"

run "$HOME/test/foo-printer.sh" PART1
assert_output "PART1: value2"
refute_output "PART1: value1"
run "$HOME/test/foo-printer.sh" PART1_PART2
assert_output "PART1_PART2: value_joined"
}

@test "exported variable should be respected in a script" {
export FOO=bar
run "$HOME/test/foo-printer.sh"
assert_output "FOO: bar"
refute_output "FOO: "
}

@test "shadowed variable should be respected in a script" {
FOO=bar run "$HOME/test/foo-printer.sh"
assert_output "FOO: bar"
refute_output "FOO: "
}

@test "exported variable should have priority over the cci-exported one" {
run cci-export FOO cci
export FOO=bar
run "$HOME/test/foo-printer.sh"
assert_output "FOO: bar"
refute_output "FOO: cci"
refute_output "FOO: "
}

@test "shadowed variable should have priority over the cci-exported one" {
run cci-export FOO cci
FOO=bar run "$HOME/test/foo-printer.sh"
assert_output "FOO: bar"
refute_output "FOO: cci"
refute_output "FOO: "
}

@test "shadowed variable should have priority over both: the exported and the cci-exported one" {
export FOO=bar-export
run cci-export FOO cci
vikin91 marked this conversation as resolved.
Show resolved Hide resolved
FOO=bar-shadow run "$HOME/test/foo-printer.sh"
assert_output "FOO: bar-shadow"
refute_output "FOO: bar-export"
refute_output "FOO: cci"
refute_output "FOO: "


run cci-export FOO cci2
export FOO=bar-export2
FOO=bar-shadow2 run "$HOME/test/foo-printer.sh"
assert_output "FOO: bar-shadow2"
refute_output "FOO: bar-export2"
refute_output "FOO: cci2"
refute_output "FOO: "
}
7 changes: 7 additions & 0 deletions images/test/foo-printer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

if [[ -n "$1" ]]; then
echo "$1: ${!1}"
else
echo "FOO: $FOO"
fi