I have a Python script that reads data from an Excel file and uploads each row as a separate document to a collection in Firestore. I want this script to run when I push a new version of the Excel file to GitHub.
I placed the necessary credentials in GitHub repo secrets and setup the following workflow to run on push to my data/ directory:
name: update_firestore
on:
push:
branches:
- main
paths:
- data/**.xlsx
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout repo content
uses: actions/checkout@v2 # checkout the repository content to github runner.
- name: setup python
uses: actions/setup-python@v4
with:
python-version: '3.*' # install the latest python version
- name: install python packages
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: execute python script
env:
TYPE: service_account
PROJECT_ID: ${{ secrets.PROJECT_ID }}
PRIVATE_KEY_ID: ${{ secrets.PRIVATE_KEY_ID }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
CLIENT_EMAIL: ${{ secrets.CLIENT_EMAIL }}
TOKEN_URI: ${{ secrets.TOKEN_URI }}
run: python src/update_database.py -n ideas -delete -add
I keep getting the following error:
Traceback (most recent call last):
File "/opt/hostedtoolcache/Python/3.10.7/x64/lib/python3.10/site-packages/firebase_admin/credentials.py", line 96, in __init__
self._g_credential = service_account.Credentials.from_service_account_info(
File "/opt/hostedtoolcache/Python/3.10.7/x64/lib/python3.10/site-packages/google/oauth2/service_account.py", line 221, in from_service_account_info
signer = _service_account_info.from_dict(
File "/opt/hostedtoolcache/Python/3.10.7/x64/lib/python3.10/site-packages/google/auth/_service_account_info.py", line 58, in from_dict
signer = crypt.RSASigner.from_service_account_info(data)
File "/opt/hostedtoolcache/Python/3.10.7/x64/lib/python3.10/site-packages/google/auth/crypt/base.py", line 113, in from_service_account_info
return cls.from_string(
File "/opt/hostedtoolcache/Python/3.10.7/x64/lib/python3.10/site-packages/google/auth/crypt/_python_rsa.py", line 171, in from_string
raise ValueError("No key could be detected.")
ValueError: No key could be detected.
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/runner/work/IRIS/IRIS/src/update_database.py", line 9, in <module>
import fire
File "/home/runner/work/IRIS/IRIS/src/fire/__init__.py", line 35, in <module>
cred = credentials.Certificate(create_keyfile_dict())
File "/opt/hostedtoolcache/Python/3.10.7/x64/lib/python3.10/site-packages/firebase_admin/credentials.py", line 99, in __init__
raise ValueError('Failed to initialize a certificate credential. '
ValueError: Failed to initialize a certificate credential. Caused by: "No key could be detected."
Error: Process completed with exit code 1.
I have tried a variety of approaches including what I show above, just hardcoding each of the secrets, and copying the .json formatted credentials directly as a single secret. I know there are some issues dealing with multiline environment variables which the PRIVATE_KEY is. I have tried:
PRIVATE_KEY str directly from the download firebase provides which includes \n-----BEGIN PRIVATE KEY-----
BunC40fL3773R5AndNumb3r5
...
rAndomLettersANDNumb3R5==
-----END PRIVATE KEY-----
I feel like the solution should be pretty straight-forward but have been struggling and my knowledge with all this is a bit limited.
Thank you in advance!
After hours of research, I found an easy way to store the Firestore service account JSON as a Github Secret.
Let's name the base-64 encoded JSON SERVICE_ACCOUNT_KEY. There are two ways to get this value:
cat path-to-your-service-account.json | base64 | xargs
This will return a single line representing the encoded service account JSON. Copy this value.
import json
import base64
service_key = {
"type": "service_account",
"project_id": "xxx",
"private_key_id": "xxx",
"private_key": "-----BEGIN PRIVATE KEY-----\nxxxxx\n-----END PRIVATE KEY-----\n",
"client_email": "xxxx.com",
"client_id": "xxxx",
"auth_uri": "xxxx",
"token_uri": "xxxx",
"auth_provider_x509_cert_url": "xxxx",
"client_x509_cert_url": "xxxx"
}
# convert json to a string
service_key = json.dumps(service_key)
# encode service key
SERVICE_ACCOUNT_KEY= base64.b64encode(service_key.encode('utf-8'))
print(SERVICE_ACCOUNT_KEY)
# FORMAT: b'a_long_string'
Copy only the value between the quotes. (copy a_long_string instead of b'a_long_string')
I am using dotenv library to read environment variables. You will have to install it first using pip install python-dotenv. Also add this dependency in your requirements.txt for github actions.
SERVICE_ACCOUNT_KEY which will store the base-64 value. - name: execute py script
env:
SERVICE_ACCOUNT_KEY: ${{ secrets.SERVICE_ACCOUNT_KEY }}
run: python src/main.py
SERVICE_ACCOUNT_KEY together with its value to your .env file (which should be in the root directory of your project). Remember to add .env to your .gitignore file to avoid exposing your key on Github.You will now need to get the value of SERVICE_ACCOUNT_KEY in your Python code and convert this value back to a JSON. I am using the dotenv library to get the value of the SERVICE_ACCOUNT_KEY.
import json
import base64
import os
from dotenv import load_dotenv, find_dotenv
# get the value of `SERVICE_ACCOUNT_KEY`environment variable
load_dotenv(find_dotenv())
encoded_key = os.getenv("SERVICE_ACCOUNT_KEY")
# decode
SERVICE_ACCOUNT_JSON = json.loads(base64.b64decode(encoded_key).decode('utf-8'))
# Use `SERVICE_ACCOUNT_JSON` later to initialse firestore db:
# cred = credentials.Certificate(SERVICE_ACCOUNT_JSON)
# firebase_admin.initialize_app(cred)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With