In GCP, how can I get a parent cloud build to fail when a child build fails? - google-cloud-build

I have a monorepo set up and a cloudbuild.yaml file in the root of my repository spins off child cloud build jobs in the first step:
# Trigger builds for all packages in the repository.
- name: "gcr.io/cloud-builders/gcloud"
entrypoint: "bash"
args: [
"./scripts/cloudbuild/build-all.sh",
# Child builds don't have the git context, so pass them the SHORT_SHA.
"--substitutions=_TAG=$SHORT_SHA",
]
timeout: 1200s # 20 minutes
The build all script is something I copied from the community builders repo:
#!/usr/bin/env bash
DIR_NAME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
set -e # Sets mode to exit on an error, without executing the remaining commands.
for d in {packages,ops/helm,ops/pulumi}/*/; do
config="${d}cloudbuild.yaml"
if [[ ! -f "${config}" ]]; then
continue
fi
echo "Building $d ... "
(
gcloud builds submit . --config=${config} $*
) &
done
wait
It waits until all child builds are done before continuing to the next one... handy!
Only problem is, if any of the child builds fail, it will still continue to the next step.
Is there a way to make this step fail if any of the child builds fail? I guess my script isn't returning the correct error code...?

The set -e flag should make the script to exit if any of the commands performed has an error, however you can also check the output of a command by using the $? variable, for example you can include the next lines:
echo "Building $d ... "
(
gcloud builds submit . --config=${config} $*
if [ $? == 1 ]; then #Check the status of the last command
echo "There was an error while building $d, exiting"
exit 1
fi
) &
So if there was an error the script will exit and give an status of 1 (error)

Related

A way to ignore exit status in gitlab job pipeline [duplicate]

In our project we have a shell script which is to be sourced to set up environment variables for the subsequent build process or to run the built applications.
It contains a block which checks the already set variables and does some adjustment.
# part of setup.sh
for LIBRARY in "${LIBRARIES_WE_NEED[#]}"
do
echo $LD_LIBRARY_PATH | \grep $LIBRARY > /dev/null
if [ $? -ne 0 ]
then
echo Adding $LIBRARY
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIBRARY
else
echo Not adding $LIBRARY
fi
done
i.e. it checks if a path to a library is already in $LD_LIBRARY_PATH and if not, adds it.
(To be fair, this could be written differently (like here), but assume the script is supposed to achieve something which is very hard to do without calling a program, checking $? and then either doing one thing or doing another thing).
The .gitlab-ci.yml then contains
before_script:
- yum install -y <various packages>
- source setup.sh
but the runner decides to stop the before script the very moment $? is non-zero, i.e. when the if-statement decides to add a path to $LD_LIBRARY_PATH.
Now it is nice that the gitlab runner checks $? after each line of my script, but here it'd be great if the lines in .gitlab-ci.yml were considered atomic.
Is there a way to avoid the intermediate checks of $? in a script that's sourced in .gitlab-ci.yml?
Use command_that_might_fail || true to mask the exit status of said command.
Also note that you can use grep -q to prevent output:
echo "$LD_LIBRARY_PATH" | grep -q "$LIBRARY" || true
This will however also mask $? which you might not want. If you want to check if the command exits correct you might use:
if echo "$LD_LIBRARY_PATH" | grep -q "$LIBRARY"; then
echo "Adding $LIBRARY"
else
...
fi
I suspect that gitlab-ci sets -e which you can disabled with set +e:
set +e # Disable exit on error
for library in "${LIBRARIES_WE_NEED[#]}"; do
...
done
set -e # Enable exit on error
Future reading: Why double quotes matter and Pitfalls with set -e
Another trick that I am using is a special kind of "|| true", combined with having access to previous exit code.
- exit_code=0
- ./myScript.sh || exit_code=$?
- if [ ${exit_code} -ne 0 ]; then echo "It failed!" ; else echo "It worked!"; fi
The $exit_code=$? always evaluates to "true" so you get a non failing command but you also receive exit_code and you can do whatever you want with it.
Note please, that you shouldn't skip the first line or exit_code will be uninitialized (since on successful run of script, the or'ed part is never executed and the if ends up being)
if [ -ne 0 ];
instead of
if [ 0 -ne 0 ];
Which causes syntax error.

Testing server setup bash scripts

I'm just learning to write bash scripts.
I'm writing a script to setup a new server.
How should I go about testing the script.
i.e.
I use apt install for certain packages like apache, php etc. and then a couple of lines down there is an error.
I then need to fix the error and run it again but it will run all the install commands again.
The system will probably say the package is installed already, but what if there are commands which append strings to files.
If these are run again it will append the same string to the file a second time.
What is the best approach to write bash-scripts like this?
Can you do test runs which rollback everything after an error or end of the script?
Or even better to have the script continue from the line where the error occured the next time it is run?
I'm doing this on an Ubuntu 18.04 server.
it's a matter of how clear you want it to be to read it, but
[ -f .step01-done ] || your install command && touch .step01-done
[ -f .step02-done ] || your other install command && touch .step02-done
maybe a little easier to read:
if ! [ -f .step01-done ]; then
if your install command ; then
touch .step01-done
fi
fi
if ! [ -f .step02-done ]; then
if your other install command ; then
touch .step02-done
fi
fi
...or something in between.
Now, I would suggest creating a directory somewhere and maybe logging output from the commands to some file there (maybe tee it) but definitely putting all these files you are creating with touch there. That way if you start it from another directory by accident, it won't matter. You just need to make sure that apt-get or whatever you use actual returns false if it fails. It should.
You could even make a function that does it in a nice way...
#!/bin/bash
function do_cmd() {
if [ -f "$1.done" ]; then
echo "$2: skipping already completed step"
return 0
fi
echo -n "$2: "
$3 1> "$1.out" 2> "$1.err"
if $?; then
echo "ok"
touch "$1.done"
return 0
else
echo "failed"
echo -e "see \"$1.out\" and/or \"$1.err\" for details."
return 1
# could "exit 1" instead
fi
}
[ -d /root/mysetup ] || mkdir /root/mysetup
if ! [ -d /root/mysetup ]; then
echo "failed to find or create /root/mysetup directory
exit 1
fi
cd /root/mysetup
# ---------------- your steps go here -------------------
do_cmd prog1 "installing prog1" "apt-get install prog1" || exit 1
do_cmd prog2 "installing prog2" "apt-get install prog2" || exit 1
do_cmd startfoo "starting foo service" "service foo start" || exit 1
echo "all setup functions finished."
You would use:
do_cmd identifier "description" "command or function"
description
identifier: unique identifier used when files are generated:
identifier.out: standard output from command
identifier.err: standard error from command
identifier.done: created when command is successful
description: this is actually printed to the terminal when the step is being executed.
command or function: this is the actual command to run
not sure why stackoverflow forced me to format that last bit as code but w/e

How to trigger a fail in Travis CI?

One of my test is a simple bash command with an if condition. I want Travis CI to consider a build as failed if the condition is positive.
I try to do it this way (a part of the .travis.yml file):
# ...
script:
- npm run build
- if [[ `git status --porcelain` ]]; then >&2 echo "Fail"; fi
# ...
But when the condition is positive, the message is just printed and the build is considered as successful.
What should I do to make a build failed when the condition is positive?
Just add exit 1; after the echo. More info.
If you just want to assert a condition but continue with testing, the following worked for me:
bash -c 'if [[ `git status --porcelain` ]]; then >&2 echo "Fail"; exit 1; fi'
This will make the build results fail but not terminate it.
Add return 1;
Travis compiles the different commands into a single bash script so exit 1 or travis_terminate 1 will abruptly interrupt the workflow and skip the after_script phase.
For complex commands that you want to make more readable and don't want to move to their own script, you can take advantage of YAML's literal scalar indicator:
script:
- |
if [[ `git status --porcelain` ]]; then
>&2 echo "Fail"
return 1
fi

bash script using && not stopping on error

i have a script that I accidentally ran without an underlying file present, and my script doesn't have a check for this file, because the script should stop when the command that requires that file exits 1.
i got caught out because it went ahead and skipped the sleep command and the ||exit 0 if test that I have as some protection protection. i would really like to know why. the if test and exit works if the preceding command doesn't fail.
if i strip the script down I can see some unexpected behaviour where the script doesn't stop at the && and skips the next sleep command.
is this not the correct way to use &&?
you can test this here:
#!/bin/bash
mkdir /root/simulatecomplexcommandthatreturns1 &&
sleep 5m
echo "let's go ahead and delete all the stuff"
find /blah/ -delete
this is on debian 9
EDIT:
for clarity, I want the script to stop when it encounters an error and I have &&. I just thought it was odd that it didn't run the sleep command.
The && only apply to next command, for a sequence, braces must be added:
#!/bin/bash
mkdir /root/simulatecomplexcommandthatreturns1 && {
sleep 5m
echo "let's go ahead and delete all the stuff"
find /blah/ -delete
}
or to avoid indent level the condition can be inverted
#!/bin/bash
mkdir /root/simulatecomplexcommandthatreturns1 || {
echo "something goes wrong"
exit 1
}
# ok, continue
sleep 5m
echo "let's go ahead and delete all the stuff"
find /blah/ -delete
If you want a script to abort/exit as soon as a command pipeline exists with a non-zero status (that means the last command in the pipeline, unless pipefail enabled), you might consider using:
set -e
In your example:
#!/bin/bash
set -e
mkdir /root/simulatecomplexcommandthatreturns1
sleep 5m
echo "let's go ahead and delete all the stuff"
find /blah/ -delete
when any of the commands fails, your script will exit.
Note however, this can sometimes lead to unwanted exits. For example it's normal for grep to exit with error if no match was found (you might "silence" such commands with grep .. || true ensuring the pipeline exits with success).
You'll probably be safer with manually testing for failure. For example:
if ! mkdir /root/simulatecomplexcommandthatreturns1; then
echo "Error description."
exit 1
fi
The usage of shortcircuiting && and || is best reserved for simple command sequences, when the execution of the next depends on successful exit of the previous. For example, the command pipeline:
mkdir /somedir && cp file /somedir && touch /somedir/file
will try to create a directory, if created successfully, it will try to copy the file; and if the file was copied successfully, it will touch the file.
Example with OR:
cp file /somedir || exit 1
where we try to copy the file and we exit if copy failed.
But you should be very careful when combining the two, since the result can be unexpected. For example:
a && b || c
is not equal to:
if a; then b; else c; fi
because c in the former expression will get executed whenever either of a or b fails (exits with a non-zero status). In the latter expression, c is executed only if a fails. For example:
true && false || echo "This also gets executed."
&& is like the AND operator with the property :
fail && <anything> equals fail
<anything> && fail equals fail
success && success equals success
So, if the first operand (or command) fails, there is no point in resolving the second command.
Hence,
when mkdir /root/simulatecomplexcommandthatreturns1 fails in
mkdir /root/simulatecomplexcommandthatreturns1 &&
sleep 5m
It skips the second command.
What you want here is || or the OR operator
fail || fail equals fail
fail || success equals success
success || <anything> equals success
So, using if the mkdir /root/simulatecomplexcommandthatreturns1 fails in"
mkdir /root/simulatecomplexcommandthatreturns1 || sleep 5m
It will have to evaluate the second operand ie the sleep 5m command.
EDIT :
Note that bash script do not exit if one of its command fails. It only exits when it reaches the end of the script or when exit is called.
if you want to exit when a certain command fails, you would do something like :
$ theCommandThatCanFail || exit 1 # the first command returns fail and
# since its `OR` operator, the second
# command will be resolved
You're using the wrong operator. What you need is || (example 2 below).
Explanation:
Note:
`A && B` # => Run `A`, and then `B` if A ran successfully.
`A || B` # => Run `A`, and then `B` if A did not run successfully.

How to execute a script from within another script?

I'm having trouble executing some files under a BASH script.
What I want is, when running the SCRIPT.SH, to check if the directory from which it's running is the right one. Which in this case it's /ROOT/OPENSOURCE . If it's not, then it asks if the user wants to move the directory into the correct place. Doing this by another script /OPENSOURCE/MODULES/MOVE.SH.
I have this variable to get the script launching dir:
spath="$( cd "$( dirname $0 )" && pwd )"
Now since the script will not be installed on the right directory, I need to run MOVE.SH which is in the MODULES directory inside OPENSOURCE. I can't get this done.
Code:
# check if script is on correct directory
if [ "$rpath" = "$spath" ]; then
Colors;
echo ${RedF}[x] [WAIT]${YellowF} Checking directory: ${Reset};
sleep 1
echo ${BlueF}[*] [Directory]:${GreenF} OK ${Reset};
else
Colors;
echo ${RedF}[x] [WAIT]${YellowF} Checking directory: ${Reset};
sleep 1
echo ${BlueF}[*] [Directory]:${RedF} FAILED: This may cause a script malfunction ${Reset};
read -p "[!] Move script directory to right directory {y|n}:" pass
if test "$pass" = "y"
then
echo ${BlueF}[*] [Directory]: ${GreenF}Ok. Moving script to new directory${Reset};
sleep 1
---- COMMAND WILL BE HERE ----
else
echo ${BlueF}[*] [Directory]: ${YellowF}Ok not moving ${Reset};
fi
fi
How can I do it ?
I'm not sure I 100% understand the question, but I think you might just be looking for the mysterious "." command, which will run another script.
Example:
test.sh:
#!/bin/bash
echo running other script $1
. $1
echo done
test2.sh:
#!/bin/bash
echo I am the other script
Run it:
> ./test.sh test2.sh
running other script test2.sh
I am the other script
done

Resources