How to inject pod environment variables values into React app on runtime? - reactjs

Running pods have some environment variables defined inside, for example:
/ # printenv
REACT_APP_ENV_VARIABLE=Variable from Kube!
REDIS_SERVICE_PORT=6379
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.38.0.1:443
REDIS_PORT=tcp://10.38.61.225:6379
REDIS_PORT_6379_TCP_ADDR=10.38.61.225
HOSTNAME=playground-pod
PLAYGROUND_SERVICE_SERVICE_HOST=10.38.0.53
REDIS_PORT_6379_TCP=tcp://10.38.61.225:6379
PLAYGROUND_SERVICE_SERVICE_PORT=80
PLAYGROUND_SERVICE_PORT=tcp://10.38.0.53:80
PLAYGROUND_SERVICE_PORT_80_TCP_ADDR=10.38.0.53
KUBERNETES_PORT_443_TCP_PROTO=tcp
PLAYGROUND_SERVICE_PORT_80_TCP_PORT=80
PLAYGROUND_SERVICE_PORT_80_TCP_PROTO=tcp
REACT_APP_ENV_VARIABLE_TWO=192.168.1.12
PLAYGROUND_SERVICE_PORT_80_TCP=tcp://10.38.0.53:80
How should I configure a React app like this one:
function App() {
return (
<div className="App">
<header className="App-header">
<p>
<code>ENV. VARIABLE: </code> {x.REACT_APP_ENV_VARIABLE}
</p>
</header>
</div>
);
}
export default App;
to read and inject some of the variables present in the pod?
The main reason I want to know it, is dynamic update of e.g. backend or Redis URLs - they might change when the app is restarted, rescheduled, etc.
My first approach was using a config.json file imported to the app, but this way I can't import dynamic values generated by running pods.

You can use the library dot-env
import React from "react";
import env from "react-dotenv";
export function MyComponent() {
return <div>{env.REACT_APP}</div>;
}
while in deployment you can pass secret from secret and configmap
spec:
containers:
- name: example-site
image: example/app:v1
ports:
- containerPort: 80
env:
- name: REACT_APP
value: "123456"
The main reason I want to know it, is dynamic update of e.g. backend
or Redis URLs - they might change when the app is restarted,
rescheduled, etc.
Above scenario fit well with your requirement, instead of using the config.json.
You can pass multiple values to deployment using configmap and secrets.

As #Harsh Manvar suggested (thanks a lot!), the react-dotenv library can be used, but just adding it to the project is not enough.
Firstly, you have to follow all steps described in react-dotenv documentation (adding .env file to your project, editing package.json file).
In my case, .env file looked like this:
REACT_APP_DEPLOY_SETUP='__dps__'
REACT_APP_PORT='__prt__'
REACT_APP_BACKEND_URL='__bur__'
These are just placeholders for real values that will be added during runtime.
Having the .env file ready, npm scripts prepended with react-dotenv command, and the variables whitelisted (as described in the library documentation), you can build your app image.
When the image is ready, add it to the kubernetes pod config file and replace your variables' placeholders with real values, like this:
[...]
spec:
containers:
- name: plg-frontend
image: localhost:5000/frontend:1.22
ports:
- containerPort: 80
command:
- sh
- -c
args:
- sed -i "s/__prt__/$REACT_APP_PORT/g" /usr/share/nginx/html/env.js;
sed -i "s/__bur__/http:\/\/$BACKEND_SERVICE_HOST/g" /usr/share/nginx/html/env.js;
sed -i "s/__dps__/$REACT_APP_DEPLOY_SETUP/g" /usr/share/nginx/html/env.js;
nginx -g 'daemon off;'
env:
- name: REACT_APP_DEPLOY_SETUP
value: "development"
- name: REACT_APP_PORT
value: "5089"
What happened up there, was replacing placeholder values with actual values:
$BACKEND_SERVICE_HOST is an environment variable that exists in the pod and can be read from the running container,
$REACT_APP_DEPLOY_SETUP is a regular string defined by user,
$REACT_APP_PORT is an integer value (it has to be in quotes, like strings!).
And the replacing happened with sed command (or rather: sh -c "sed -i ..."). All of the commands are chained, so don't forget about semicolons at the end of each argument.
All of the replacements were made in /usr/share/nginx/html/env.js file, which is created by react-dotenv library in project root. The actual location depends on where you mounted your build image (it's defined in Dockerfile).
Lastly, nginx command is called, since this is the final command invoked in image's Dockerfile. Without this explicit call, the command from the Dockerfile would be overwritten with the commands related to the pod container and, in this case, nginx wouldn't start your app.
After the pod is started, you can check whether the variables are present in the container:
kubectl exec <pod-name> -- printenv | grep REACT_APP
But it doesn't mean they were read by your app during runtime. To see if they were changed to the values from the pod definition, you can either exec running pod container and preview the env.js file or add some console logging in the app code.

Related

How do you assign an Array inside a Dockerfile?

I have tried a number of different ways to assign an array inside a RUN command within a Dockerfile. None of them seem to work. I am running on Ubuntu-Slim, with bash as my default shell.
I've tried this (second line below)
RUN addgroup --gid 1000 node \
&& NODE_BUILD_PACKAGES=("binutils-gold" "g++" "gcc" "gnupg" "libgcc-7-dev" "linux-headers-generic" "make" "python3" ) \
...
But it fails with /bin/sh: 1: Syntax error: "(" unexpected.
I also tried assigning it as an ENV variable, as in:
ENV NODE_BUILD_PACKAGES=("binutils-gold" "g++" "gcc" "gnupg" "libgcc-7-dev" "linux-headers-generic" "make" "python3" )
but that fails as well.
Assigning and using arrays in Bash is completely supported. Yet, it appears that I can't use that feature of Bash when running in a Dockerfile. Can someone confirm/deny that you can assign arrays variables inside of shell commands in Dockerfile RUN syntax (or ENV variable syntax)?
The POSIX shell specification does not have arrays. Even if you're using non-standard shells like GNU bash, environment variables are always simple strings and never hold arrays either.
The default shell in Docker is usually /bin/sh, which should conform to the POSIX spec, and not bash. Alpine-based images don't have bash at all unless you go out of your way to install it. I'd generally recommend trying to stick to the POSIX syntax whenever possible.
A typical Dockerfile is fairly straightforward; it doesn't have a lot of parts that get reused multiple times and most of the things you can specify in a Dockerfile you don't need to be user-configurable. So for a list of OS packages, for example, I'd just list them out in a RUN command and not bother trying to package them into a variable.
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive \
apt-get install --no-install-recommends --assume-yes \
binutils-gold \
g++ \
gcc \
...
Other things I see in Stack Overflow questions that do not need to be parameterized include the container path (set it once as the WORKDIR and refer to . thereafter), the process's port (needs to be a fixed number for the second docker run -p part), and user IDs (can be overridden with docker run -u, and you don't usually want to build an image that can only run on one system).
WORKDIR /app # not an ENV or ARG
COPY . . # into the WORKDIR, do not need to repeat
RUN adduser node # with no specific uid
EXPOSE 3000 # a fixed port number
RUN mkdir /data # also use a fixed path for potential mount points
You can have array NODE_BUILD_PACKAGES in RUN if you define SHELL :
SHELL ["/bin/bash", "-c"]

Google Cloud Build: Moving files

I want to move the file index.js from the root of the project to the dist/project_name. This is the step from cloudbuild.yaml:
- name: 'gcr.io/cloud-builders/docker'
entrypoint: /bin/bash
args: ['-c', 'mv', 'index.js', 'dist/project_name']
But the step is failing with the next error:
Already have image (with digest): gcr.io/cloud-builders/docker
mv: missing file operand
Try 'mv --help' for more information.
How I can fix this issue?
Because you're using bash -c, I think you need to encapsulate the entire "script" in a string:
args: ['-c', 'mv index.js dist/project_name']
My personal preference (and it's just that), is to not embed JSON ([...]) in YAML. This makes the result in this case slightly clearer and makes it easier to embed a multiline script:
args:
- bash
- -c
- |
mv index js dist/project_name
NOTE tools like YAMLlint will do this for you too.

Optimal usage of codecov in a monorepo context with separate flags for each package

I was just wondering what’s the best way to configure codecov for a monorepo setting. For example, let’s say I have packages A and B under my monorepo. The way I’m currently using codecov is by using a github action codecov/codecov-action#v1, by using multiple uses statement in my GitHub workflow YAML file like the following:-
- uses: codecov/codecov-action#v1
with:
files: ./packages/A/coverage/lcov.info
flags: flag_a
name: A
- uses: codecov/codecov-action#v1
with:
files: ./packages/B/coverage/lcov.info
flags: flag_b
name: B
I know it's possible to use a comma-separated value to upload multiple files, but I have to set a separate flag for each package, and doing it that way doesn't seem to work.
Thank you.
If anyone wants to know my solution, heres what I came up with.
I ended up replacing the github action with my own bash script.
final code
#!/usr/bin/env bash
codecov_file="${GITHUB_WORKSPACE}/scripts/codecov.sh"
curl -s https://codecov.io/bash > $codecov_file
chmod +x $codecov_file
cd "${GITHUB_WORKSPACE}/packages";
for dir in */
do
package="${dir/\//}"
if [ -d "$package/coverage" ]
then
file="$PWD/$package/coverage/lcov.info"
flag="${package/-/_}"
$codecov_file -f $file -F $flag -v -t $CODECOV_TOKEN
fi
done
this is what the above bash script does
Downloading the bash uploader script from codecov
Moving to the packages directory where are the packages are located, and going through all the 1st level directories
Change the package name by removing extra slash
If the directory contains coverage directory only then enter into it, since only those packages have been tested.
Create a file and flag variable (removing hypen with underscore as codecov doesn't support hypen in flag name)
Executed the downloaded codecov script by passing the file and flag variable as argument

How can I pass environment variables to mongo docker-entrypoint-initdb.d?

I am trying to do the following tutorial:
https://itnext.io/docker-mongodb-authentication-kubernetes-node-js-75ff995151b6
However, in there, they use raw values for the mongo init.js file that is placed within docker-entrypoint-initdb.d folder.
I would like to use environment variables that come from my CI/CD system (Gitlab). Does anyone know how to pass environment variables to the init.js file? I have tried several things like for example use init.sh instead for the shell but without any success.
If I run manually the init shell version, I can have it working because I call mongo with --eval and pass the values, however, the docker-entrypoint-blabla is called automatically, so I do not have control of how this is called and I do not know what I could do for achieving what I want.
Thank you in advance and regards.
you can make use of a shell script to retrieve env variables and create the user.
initdb.d/init-mongo.sh
set -e
mongo <<EOF
use $MONGO_INITDB_DATABASE
db.createUser({
user: '$MONGO_INITDB_USER',
pwd: '$MONGO_INITDB_PWD',
roles: [{
role: 'readWrite',
db: '$MONGO_INITDB_DATABASE'
}]
})
EOF
docker-compose.yml
version: "3.7"
services:
mongodb:
container_name: "mongodb"
image: mongo:4.4
hostname: mongodb
restart: always
volumes:
- ./data/mongodb/mongod.conf:/etc/mongod.conf
- ./data/mongodb/initdb.d/:/docker-entrypoint-initdb.d/
- ./data/mongodb/data/db/:/data/db/
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=root
- MONGO_INITDB_DATABASE=development
- MONGO_INITDB_USER=mongodb
- MONGO_INITDB_PWD=mongodb
ports:
- 27017:27017
command: [ "-f", "/etc/mongod.conf" ]
Now you can connect to development database using mongodb as user and password credentials.
Use shell script (e.g mongo-init.sh) to access variables. Can still run JavaScript code inside as below.
set -e
mongo <<EOF
use admin
db.createUser({
user: '$MONGO_ADMIN_USER',
pwd: '$MONGO_ADMIN_PASSWORD',
roles: [{
role: 'readWrite',
db: 'dummydb'
}]
})
EOF
Shebang line is not necessary at the beginning as this file will be sourced.
Until recently, I simply used a .sh shell script in the docker-entrypoint-initdb.d directory to access ENV variables, much like #Lazaro answer.
It is now possible to access environment variables from javascript files using process.env, provided the file is run with the newer mongosh instead of mongo, which is now deprecated.
However, according to the Docs (see 'Initializing a fresh instance'), mongosh is only used for .js files in docker-entrypoint-initdb.d if using version 6 or greater. I can confirm this is working using the mongo:6 image tag.
You can use envsubs.
If command not found : here. Install it on your runners host if you use shell runners, else, within the docker image used by the runner, or directly in your script.
(NB: Your link isn't free, so I can't adapt to your situation :p )
Example:
init.js.template:
console.log('$GREET $PEOPLE $PUNCTUATION')
console.log('Pipeline from $CI_COMMIT_BRANCH')
gitlab_ci.yml:
variables:
GREET: "hello"
PEOPLE: "world"
PUNCTUATION: "!"
# ...
script:
- (envsubst < path/to/init.js.template) > path/to/init.js
- cat path/to/init.js
Output:
$ (envsubst < init.js.template) > init.js
$ cat init.js
console.log('hello world !')
console.log('Pipeline from master')
At the end the answer is that you can use a .sh file instead of a .js file within the docker-entrypoint-initdb.d folder. Within the sh script, you can use directly environment variables. However, I could not do that at the beginning because I had a typo and environment variables were not created properly.
I prefer this method because it allows you to keep a normal .js file which you lint instead of embedding the .js file into a string.
Create a dockerfile like so:
FROM mongo:5.0.9
USER mongodb
WORKDIR /docker-entrypoint-initdb.d
COPY env_init_mongo.sh env_init_mongo.sh
WORKDIR /writing
COPY mongo_init.js mongo_init.js
WORKDIR /db/data
At the top of your mongo_init.js file, you can just define variables you need
db_name = DB_NAME
schema_version = SCHEMA_VERSION
and then in your env_init_mongo.sh file, you can just replace the strings you need with environment variables or add lines to the top of the file:
mongo_init="/writing/mongo_init.js"
sed "s/SCHEMA_VERSION/$SCHEMA_VERSION/g" -i $mongo_init
sed "s/DB_NAME/${MONGO_INITDB_DATABASE}/g" -i $mongo_init
sed "1s/^/use ${MONGO_INITDB_DATABASE}\n/" -i $mongo_init # add to top of file
mongo < $mongo_init

tried to add a new update.secondary hook to my repos in gitolite and now git push fails

remote: Undefined subroutine &main::repo_rights called at hooks/update line 41.
remote: error: hook declined to update
I have removed the update hook from all of my repos in order to get around this, but I know that they are now wide open.
I ran gl-setup, and I may have mixed versions of gitolite on my machine. I am afraid that I ran the gl-setup from a version that is different than the one I am running currently. I am not sure how to tell. Please help. :-(
Update, for a more recent version of Gitolite (namely a V3.x or more), the official documentation would be: "adding your own update hooks", and it uses VREFs (virtual refs).
add this line in the rc file, within the %RC block, if it's not already present, or uncomment it if it's already present and commented out:
LOCAL_CODE => "$ENV{HOME}/local",
copy your update hook to a subdirectory called VREF under this directory, giving it a suitable name (let's say "crlf"):
# log on to gitolite hosting user on the server, then:
cd $HOME
mkdir -p local/VREF
cp your-crlf-update-hook local/VREF/crlf
chmod +x local/VREF/crlf
in your gitolite-admin clone, edit conf/gitolite.conf and add lines like this:
- VREF/crlf = #all
to each repo that should have that "update" hook.
Alternatively, you can simply add this at the end of the gitolite.conf file:
repo #all
- VREF/crlf = #all
Either way, add/commit/push the change to the gitolite-admin repo.

Resources