Use case
[For docker containers deployed to Cloud Run], run unit tests at build time, some of which require secrets and some level of google authentication. The unit tests might require authentication in order to interact with google services such as cloud storage, or retrieve secret keys from google KMS in order to test interactions with other authenticated APIs. Based on my understanding of gcloud authentication, the only way to provide credentials at build time is by a credentials json file for your service account (whereas when the deployed service is triggered, ADC can use the service account provided by Cloud Run).
Current Approach
Assuming we have generated a credentials file for our service account locally
- Check whether the build is initiated locally or by a github trigger. If the build is triggered by a github trigger, only run unit tests that don't require authentication, as I have yet to discover a way to get credentials at build time in this case.
- [Proceed only if this is a locally triggered build]. Copy credentials file contents as base64 encoded string into the dockerfile via build argument
- Create temporary credentials file during build by decoding the base64 encoded build argument
- Run all unit tests using that credentials file
- Delete the credentials file as it is not needed in the deployed container
Current Implementation
[Gross and probably unsafe code trigger warning]
$cloud-build-local --config=build_config/cloudbuild_manual.json \
--dryrun=false \
--push \
--substitutions=\
_JSON_CONTENTS="$$(cat $(CREDENTIALS_PATH) | base64)",\
_CONTAINER_ID="$(CONTAINER_ID)" \
.
cloudbuild_manual.json
{
"name": "gcr.io/cloud-builders/docker",
"args": [
"build",
"--build-arg",
"JSON_CONTENTS=${_JSON_CONTENTS}",
"--build-arg",
"PROJECT_ID=${PROJECT_ID}",
"--build-arg",
"RUN_AUTHENTICATED_TESTS=true",
"-t",
"gcr.io/${PROJECT_ID}/${_CONTAINER_ID}",
"."
],
"env":[],
"timeout": "500s"
}
...
Dockerfile
ARG RUN_AUTHENTICATED_TESTS=false
ENV CREDENTIALS_TEMP_LOCATION credentials_temp.json
ARG JSON_CONTENTS
...
RUN if [ "$RUN_AUTHENTICATED_TESTS" = "true" ] ; then \
echo "${JSON_CONTENTS}" | base64 -d > $CREDENTIALS_TEMP_LOCATION && \
PYTHONPATH=$PYTHONPATH:`pwd`/ pytest -v --run-auth && \
rm $CREDENTIALS_TEMP_LOCATION ; \
else \
PYTHONPATH=$PYTHONPATH:`pwd`/ pytest -v ; \
fi;
...
Problems with this approach
- It makes me feel gross inside
- Doesn't provide a solution that works for continuous deployment via github triggers
- Possibly unsafe?
Possible Solution
Use vcrpy or a similar data mocking library to intercept all unit tested interactions and replace them with recorded versions, thus eliminating the need to have any sort of authentication at build time. Ensure that all tokens and secret keys are censored from the recordings for security (avoiding storing API keys in plaintext in the git repo).
I would love to hear your thoughts on my current approach, alternative approach, and any other solutions you may have. Feel free to trash talk my trash solution :)