Trailing newline included in variable assignment after grep, and sent to GitHub actions output - bash

I've got a bash script in a GitHub action that greps for image tags, then replaces them in a docker-compose.yml.
# docker-compose.yml from a JSON payload
docker_compose='"services:\n api:\n proxy: ghcr.io/org/api:main-4095094301\n ports:\n - 0:80\n restart: always\n\n proxy:\n image: ghcr.io/org/proxy:main-4095124301\n depends_on:\n - api\n environment:\n - \"ENVIRONMENT=dev\"\n - \"PORT=8000\"\n ports:\n - 0:3000\n restart: always\n\n ..."'
# Extract the tag of each image, send to GitHub actions output
echo "proxy_tag=$(echo -n $docker_compose | grep -oP 'proxy:\K[-\w]+')" >> $GITHUB_OUTPUT
If I remove the >> operator to echo the output, everything looks OK:
proxy_tag=main-4095094301
But when I feed it into sed later in a different step of the pipeline, an extra newline character seems to come from nowhere:
echo "running s/PROXY_TAG/$proxy_tag/"
sed -i "s/PROXY_TAG/$proxy_tag/" docker-compose.yml
# running s/PROXY_TAG/main-4095094301
# /
# sed: -e expression #1, char 18: unterminated `s' command
# Error: Process completed with exit code 1.
I've tried some common suggestions eg. piping output through tr -d '\n':
echo "proxy_tag=$(echo -n $docker_compose | grep -oP 'proxy:\K[-\w]+' | tr -d '\n')" >> $GITHUB_OUTPUT
Is there something missing I don't understand about bash vars or Github actions?
See below a more complete context of how where these commands are being used.
name: Update Docker Compose
on:
workflow_dispatch:
inputs:
proxy_tag:
description: proxy_tag
type: string
required: false
# api_tag:
# site_tag:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v3
- name: Fetch docker-compose
id: get-tags
run: |
docker_compose=$(curl ...)
echo "proxy_tag=$(echo -n $docker_compose | grep -oP 'proxy:\K[-\w]+')" >> $GITHUB_OUTPUT
echo "api_tag=$(echo $docker_compose | grep -oP 'api:\K[-\w]+')" >> $GITHUB_OUTPUT
echo "site_tag=$(echo $docker_compose | grep -oP 'site:\K[-\w]+')" >> $GITHUB_OUTPUT
- name: Replace tags docker-compose
env:
proxy_tag: >
${{ github.event_name == 'workflow_dispatch'
&& github.event.inputs.proxy_tag
|| steps.get-tags.outputs.proxy_tag
|| 'latest'
}}
# api_tag: >
# site_tag: >
run: |
echo "Setting [$proxy_tag]"
sed -i "s/PROXY_TAG/$proxy_tag/" docker-compose.yml
sed -i "s/API_TAG/$api_tag/" docker-compose.yml
sed -i "s/SITE_TAG/$site_tag/" docker-compose.yml
# Outputs:
# Setting [main-4095094301 # <--- notice the newline
# ]
#
# sed: -e expression #1, char 18: unterminated `s' command
# Error: Process completed with exit code 1.

I suspect that the newline character being added is from the YAML's folding using > while setting your environment variables under env. Try >- to not add (strip) the last newline character.
This (https://yaml-multiline.info/) might be helpful for live experimentation with YAML.

Worst case, if you're using bash,
$: proxy_tag="main-4095094301
" # embedded newline for example
$: echo "[$proxy_tag]" # show with embedded newline
[main-4095094301
]
$: echo "[${proxy_tag%$'\n'}]" # show with embedded newline removed
[main-4095094301]
$: echo PROXY_TAG | sed "s/PROXY_TAG/$proxy_tag/" # syntax error with embedded newline
sed: -e expression #1, char 27: unterminated `s' command
$: echo PROXY_TAG | sed "s/PROXY_TAG/${proxy_tag%$'\n'}/" # removed, syntax ok
main-4095094301
That seems error-prone to me, though, and I prefer to do most of my basic string processing right in the interpreter unless it's huge and I need it faster, so I'd probably do something based on this -
$: docker_compose=$'services:\n api:\n proxy: ghcr.io/org/api:main-4095094301\n ports:\n - 0:80\n restart: always\n\n proxy:\n image: ghcr.io/org/proxy:main-4095124301\n depends_on:\n - api\n environment:\n - "ENVIRONMENT=dev"\n - "PORT=8000"\n ports:\n - 0:3000\n restart: always\n\n ...' # note $'...' for clean quotes and newlines
$: echo "[$docker_compose]"
[services:
api:
proxy: ghcr.io/org/api:main-4095094301
ports:
- 0:80
restart: always
proxy:
image: ghcr.io/org/proxy:main-4095124301
depends_on:
- api
environment:
- "ENVIRONMENT=dev"
- "PORT=8000"
ports:
- 0:3000
restart: always
...]
$: shopt -s extglob # enable extended globbing
$: tmp="${docker_compose##* proxy: *([^:]):}" # trim front - *(...) is "0 or more"
$: proxy_tag="${tmp%%$'\n'*}" # trim after, from 1st newline on
$: echo "[$proxy_tag]" # show that it's clean
[main-4095094301]

Related

grep for a pattern "variable=value" and returning only matching entries having a value > threshold

I am searching a kubernetes pod logs for the pattern “variable=value” ( e.g., variable=10 or variable=500) using the command below:
Kubectl logs -f | grep “variable=”
My question is that wether it is possible to modify the command above in order to return the logs where the variable value is greater than some threshold, e.g, for threshold=300, variable=301, variable=302 would be filetered in, but variable=299 would be filtered out
I know I can develop a small program for this, but rather I want a rapid solution in the command line direcly without the hassle of writing a small prgram.
You didn't provide any sample input so it's a guess but this may be what you're trying to do:
awk -F'=' '($1=="variable") && ($2>300)' file
If that's not all you need then please edit your question to include a Minimal, Reproducible Example with concise, testable sample input, expected output and your attempt to solve the problem yourself so we can help you further. See [ask] and look at existing questions that have been upvoted and answered for examples.
As a test, I've created this file:
Prompt> cat test.txt
var=2
var=22
var=222
blabla
First, I filter our the variable assignment lines:
Prompt> grep "var=" test.txt
var=2
var=22
var=222
Then, I filter on the condition of the values, in two ways:
Prompt> grep "var=" test.txt | awk -F '=' '{if ($2 > 25) print $1 "=" $2}'
var=222
Prompt> grep "var=" test.txt | awk -F '=' '{if ($2 < 25) print $1 "=" $2}'
var=2
var=22
I would use perl, here I am printing the logs from a pod called bash-pod. Filtering, only the lines with threshold=<integer-greater-than-80> are present.
kubectl logs -f bash-pod |perl -ne '/.*threshold=(\d+).*/m;print if $1> 80'
The above code is tested with the below pod:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: bash-pod
name: bash-pod
spec:
containers:
- image: bash
name: bash-pod
resources: {}
command: ['bash','-c','while true;do echo "threshold=$(( ( RANDOM % 100 ) + 1 ))";sleep 1;done']
dnsPolicy: ClusterFirst
restartPolicy: Never
status: {}
Output:
kubectl logs -f bash-pod |perl -ne '/.*threshold=(\d+).*/m;print if $1> 80'
threshold=82
threshold=90
threshold=89
threshold=90
threshold=85
threshold=83
threshold=86
threshold=83
...
.....
Note: If you want to take float into consideration then you can use .*threshold=(\d+(?:\.\d+)?).* as new regex.
Here's a flexible solution that allows you specify parameters you like, and functions the same :
< test_filter_threshold.txt |
mawk 'NF*=(____=="<")!=((___~"."?+___:___\
)<=+$(NF*=(!_<NF)*(__==$!_)))' FS== OFS== \
__="var" ___="1" ____=">="
var=2
var=22
var=222
___=4 ____='<'
var=2
___=200 ____='<'
var=2
var=22
___=200 ____='>='
var=222
___=200 ____= # "<" is strictly less than,
# all else def. to ">="
var=222
___='3.14159' ____='>=' # float point thresholds (TRHD) are valid
var=22
var=222
___= ____= # missing TRHD def. to all
var=2
var=22
var=222
___='abc' ____='>=' # invalid TRHD def. to non-zero positive
var=2
var=222
1 var=2
2 var=-22
3 var=222
4 blabla

How do I embed YAML inside a YAML as text in bash script?

I am assembling a YAML build pipeline using bash as follows.
cat <<EOT >> ${BUILD_SOURCESDIRECTORY}/azure-pipelines.yml
- template: templates/deploy-to-env-ssh.yml#infrastructure
parameters:
dockerHostEndpoint: ${DOCKER_HOST}
jobName: ${BASENAME}
stackName: ${STACK_NAME}
composeFile: ${STACK_NAME}.yml
schedule: ???
$(cat schedule.yml)
tz: ${TZ}
EOT
What I want is to store the following YAML into schedule as a string which I can reuse in a another part of the pipeline.
version: 1.4
jobs:
DockerJob:
cmd: docker ps
time: "*"
notifyOnSuccess:
- type: stdout
data:
- stdout
But it seems it needs to be indented.
You can use the pr utility:
cat <<EOF >> ${BUILD_SOURCESDIRECTORY}/azure-pipelines.yml
- template: templates/deploy-to-env-ssh.yml#infrastructure
parameters:
dockerHostEndpoint: ${DOCKER_HOST}
jobName: ${BASENAME}
stackName: ${STACK_NAME}
composeFile: ${STACK_NAME}.yml
schedule: $(printf "\n" && pr -to 8 schedule.yml)
tz: ${TZ}
EOT
I use printf "\n" because you'd need to place the $(…) at the first column if you want to write $(…) on a new line, since every line including the first one will be offset by the given number of spaces.

Replace a value in yaml file with multi line string using sed

I have been trying to replace/add value of a field in yaml with an env variable that has multi line string, using below syntax
replacewith=" |-
multi
line
string"
sed -i -e "s/^\(\s*key-in-yaml\s*:\s*\).*/\1 $replacewith/" somefile.yaml
We can assume that key-in-yaml doesn't have any value by default. Above comand results in
sed: -e expression #1, char 37: unterminated `s' command
I also want the indentation to be maintained.
If this is the content of yaml file
apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
annotations:
alm-examples:
capabilities: Basic Install
after that sed command i was expecting
apiVersion: operators.coreos.com/v1alpha1
kind: ClusterServiceVersion
metadata:
annotations:
alm-examples: |-
multi
line
string
capabilities: Basic Install
With your shown samples, please try following, in case you are ok with awk.
awk -v str="$replacewith" '
1;
/annotations:/{ found=1 }
found && /alm-examples: \|-/{
print str
found=""
}
' Input_file
Once you are happy with above results(which will be shown on terminal) and in case you want to save them into Input_file itself then append > temp && mv temp Input_file to above command.
sed -E '/alm-examples/s/(.*)$/printf "\1 $replacewith"/e' somefile.yaml
This uses s///e to shell out to printf which will handle the multiline string better than attempts to inline it with sed commands. It is printf expanding the string, not sed, because the sed command is in single quotes.
This also works, with & replacing the \1, because the line break doesn't get passed to printf either way:
sed -E '/alm-examples/s/.*/printf "& $replacewith"/e' somefile.yaml
Or, to depend on annotations: in the prior line:
sed -E '/annotations:/{N;/alm-examples/s/.*/printf "& $replacewith"/e}' somefile.yaml
I have decided to use yq and it has been working great even for complex fields in yaml for example a.b.c[0].d=env-var-multiline.

Saving AWS Environment Variables to File - How to handle special character '&'

I am trying to run a django manage.py task via cron, on AWS Elasticbeanstalk (EB).
*/10 * * * * root /usr/local/bin/notification_cron.sh > /dev/null
The notification_cron.sh script calls a django manage.py task.
Django needs EB's environment variables (like RDS_PORT, RDS_DB_NAME, RDS_PASSWORD etc). So I am saving these environment variables into a file at deployment, and reloading those in the bash script that also calls the manage.py task.
This is part of my deployment config in .ebextensions:
commands:
001_envvars_to_bash_source_file:
command: |
# source our elastic beanstalk environment variables
/opt/elasticbeanstalk/bin/get-config --output YAML environment|perl -ne "/^\w/ or next; s/: /=/; print qq|\$_|" > /usr/local/bin/envvars
chmod 755 /usr/local/bin/envvars
files:
"/usr/local/bin/notification_cron.sh":
mode: "000755"
owner: root
group: root
content: |
#!/usr/bin/env bash
AWS_CONFIG_FILE="/home/ec2-user/.aws/config"
set -o allexport
# Loading environment data
source /usr/local/bin/envvars
set +o allexport
cd /opt/python/current/app/
source /opt/python/run/venv/bin/activate
python manage.py my_management_task
The problem is caused by the line
/opt/elasticbeanstalk/bin/get-config --output YAML environment|perl -ne "/^\w/ or next; s/: /=/; print qq|\$_|" > /usr/local/bin/envvars
or the alternate sed equivalent
/opt/elasticbeanstalk/bin/get-config environment --output yaml | sed -n '1!p' | sed -e 's/^\(.*\): /\1=/g' > /usr/local/bin/envvars
The contents of /usr/local/bin/envvars are not always in quotes:
PYTHONPATH="/opt/python/current/app/mydjangoapp:$PYTHONPATH"
DJANGO_SETTINGS_MODULE=mydjangoapp.settings
AWS_ACTIVE='true'
RDS_PORT='5432'
RDS_HOSTNAME=hostname.host.us-east-1.rds.amazonaws.com
RDS_USERNAME=master
RDS_DB_NAME=ebdb
RDS_PASSWORD=My&Password
This causes trouble where an environment variable has the "&" character.
'RDS_PASSWORD': My&Password
Bash splits this up at the "&" character when I source /usr/local/bin/envvars to import them back into my script before calling django's manage.py.
Phew. My question is:
How do I get RDS_PASSWORD="My&Password" in the /usr/local/bin/envvars file (note the double quotes required) without breaking the other lines like RDS_PORT='5432'?
Using GNU sed you can do something like below,
sed -r 's/RDS_PASSWORD=([[:graph:]]+)/RDS_PASSWORD="\1"/' /usr/local/bin/envvars
PYTHONPATH="/opt/python/current/app/mydjangoapp:$PYTHONPATH"
DJANGO_SETTINGS_MODULE=mydjangoapp.settings
AWS_ACTIVE='true'
RDS_PORT='5432'
RDS_HOSTNAME=hostname.host.us-east-1.rds.amazonaws.com
RDS_USERNAME=master
RDS_DB_NAME=ebdb
RDS_PASSWORD="My&Password"
You can then add the -i flag to sed to in-place substitution. I have used the character class [[:graph:]] which is
‘[:graph:]’
Graphical characters: ‘[:alnum:]’ and ‘[:punct:]’.
The special characters as part of [:punct:] are
! " # $ % & ' ( ) * + , - . / : ; < = > ? # [ \ ] ^ _ ` { | } ~
So the sed can handle the substitution if any of these above characters constitute the RDS_PASSWORD variable.
The syntax you can use, is
/opt/elasticbeanstalk/bin/get-config environment --output yaml | \
sed -n '1!p' | sed -e 's/^\(.*\): /\1=/g' | \
sed -r 's/RDS_PASSWORD=([[:graph:]]+)/RDS_PASSWORD="\1"/' > /usr/local/bin/envvars

To remove line based on string

I have file like test.yaml file, the text content in the file like below.
servers:
- uri: "http://demo.nginx1.com/status"
name: "NGinX Monitor1"
- uri: "http://demo.nginx2.com/status"
name: "NGinX Monitor2"
I want to remove - uri line and immediate next line (start with name:) where host name = demo.nginx1.com.
I want out put like below.
servers:
- uri: "http://demo.nginx2.com/status"
name: "NGinX Monitor2"
I tied like below..
cat test.yaml | grep -v demo.nginx1.com | grep -v Monitor1 >> test_back.yaml
mv test_back.yaml test.yaml
I am getting expected out put. But it's re creating the file and I don't want to re create the file
Please help me with suitable command that i can use..
Just a simple logic using GNU sed
sed '/demo.nginx1.com/,+1 d' test.yaml
servers:
- uri: "http://demo.nginx2.com/status"
name: "NGinX Monitor2"
For in-place replacement, add a -i flag as -i.bak
sed -i.bak '/demo.nginx1.com/,+1 d' test.yaml
To see the in-place replacement:-
cat test.yaml
servers:
- uri: "http://demo.nginx2.com/status"
name: "NGinX Monitor2"
As I dislike using regular expressions to hack something you can parse - here's how I'd tackle it, using perl and the YAML module:
#!/usr/bin/env perl
use strict;
use warnings;
use YAML;
use Data::Dumper;
#load yaml by reading files specified as args to stdin,
#or piped in. (Just like how you'd use 'sed')
my $config = Load ( do { local $/ ; <>} );
#output data structure for debug
print Dumper $config;
#use grep to filter the uri you don't want.
#{$config -> {servers}} = grep { not $_ -> {uri} =~ m/demo.nginx2/ } #{$config -> {servers}};
#resultant data structure
print Dumper $config;
#output YAML to STDOUT
print Dump $config;

Resources