Summary
What specific syntax must be changed in the code below in order for the multi-line contents of the $MY_SECRETS
environment variable to be 1.) successfully written into the C:\Users\runneradmin\somedir\mykeys.yaml
file on a Windows runner in the GitHub workflow whose code is given below, and 2.) read by the simple Python 3 main.py
program given below?
PROBLEM DEFINITION:
The echo "$MY_SECRETS" > C:\Users\runneradmin\somedir\mykeys.yaml
command is only printing the string literal MY_SECRETS
into the C:\Users\runneradmin\somedir\mykeys.yaml
file instead of printing the multi-line contents of the MY_SECRETS
variable.
We confirmed that this same echo
command does successfully print the same multi-line secret in an ubuntu-latest runner, and we manually validated the correct contents of the secrets.LIST_OF_SECRETS
environment variable. … This problem seems entirely isolated to either the windows command syntax, or perhaps to the windows configuration of the GitHub windows-latest
runner, either of which should be fixable by changing the workflow code below.
EXPECTED RESULT:
The multi-line secret should be printed into the C:\Users\runneradmin\somedir\mykeys.yaml
file and read by main.py
.
The resulting printout of the contents of the C:\Users\runneradmin\somedir\mykeys.yaml
file should look like:
***
***
***
***
LOGS THAT DEMONSTRATE THE FAILURE:
The result of running main.py
in the GitHub Actions log is:
ccc item is: $MY_SECRETS
As you can see, the string literal $MY_SECRETS
is being wrongly printed out instead of the 4 ***
secret lines.
REPO FILE STRUCTURE:
Reproducing this error requires only 2 files in a repo file structure as follows:
.github/
workflows/
test.yml
main.py
WORKFLOW CODE:
The minimal code for the workflow to reproduce this problem is as follows:
name: write-secrets-to-file
on:
push:
branches:
- dev
jobs:
write-the-secrets-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- shell: python
name: Configure agent
env:
MY_SECRETS: ${{ secrets.LIST_OF_SECRETS }}
run: |
import subprocess
import pathlib
pathlib.Path("C:\Users\runneradmin\somedir\").mkdir(parents=True, exist_ok=True)
print('About to: echo "$MY_SECRETS" > C:\Users\runneradmin\somedir\mykeys.yaml')
output = subprocess.getoutput('echo "$MY_SECRETS" > C:\Users\runneradmin\somedir\mykeys.yaml')
print(output)
os.chdir('D:\a\myRepoName\')
mycmd = "python myRepoName\main.py"
p = subprocess.Popen(mycmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while(True):
# returns None while subprocess is running
retcode = p.poll()
line = p.stdout.readline()
print(line)
if retcode is not None:
break
MINIMAL APP CODE:
Then the minimal main.py
program that demonstrates what was actually written into the C:\Users\runneradmin\somedir\mykeys.yaml
file is:
with open('C:\Users\runneradmin\somedir\mykeys.yaml') as file:
for item in file:
print('ccc item is: ', str(item))
if "var1" in item:
print("Found var1")
STRUCTURE OF MULTI-LINE SECRET:
The structure of the multi-line secret contained in the secrets.LIST_OF_SECRETS
environment variable is:
var1:value1
var2:value2
var3:value3
var4:value4
These 4 lines should be what gets printed out when main.py
is run by the workflow, though the print for each line should look like ***
because each line is a secret.
4
Answers
You need to use
yaml
library:This is result:
I used this.
I tried the following code and it worked fine :
LIST_OF_SECRETS
Github action (test.yml)
Output
As you also mention in the question, Github will obfuscate any printed value containing the secrets with
***
EDIT : Updated the code to work with multiple line secrets. This answer was highly influenced by this one
Edit: updated with fixed
main.py
and how to run it.You can write the key file directly with Python:
To avoid newline characters in your output, you need a
main.py
that removes the newlines (here with.strip().splitlines()
):main.py
Here’s the input:
And the output:
Here is my complete workflow:
Also, a simpler version using only Windows shell (Powershell):
The problem is – as it is so often – the quirks of Python with byte arrays and strings and en- and de-coding them in the right places…
Here is what I used:
test.yml:
main.py:
secrets.LIST_OF_SECRETS:
And my output in the log was