Chef recipe - not_if with SQL query result - ruby

I have in my recipe this block:
bash "Create admin database tables" do
code "mysql -u root -D admindb < /vagrant/files/admin.sql"
not_if shell_out("mysql -u root -s --skip-column-names -e 'SELECT COUNT(DISTINCT table_name)>0 FROM information_schema.columns WHERE table_schema = \"admindb\";'").stdout().chomp()
end
Result of select query is 0 or 1 written to stdout (nothing more). I would like to run shell command only, if result of query will be 0. How can I achieve it?

You just need to use a Ruby equality operator inside the block. Since you said "only if" the output is 0, it would be simpler to do this with only_if rather than not_if:
only_if { shell_out("mysql -u root -s --skip-column-names -e 'SELECT COUNT(DISTINCT table_name)>0 FROM information_schema.columns WHERE table_schema = \"admindb\";'").stdout.chomp == '0' }

Related

Check if a role exists in PostgreSQL using psql

I need in a bash script a IF condition on the existence of a role in a PostgreSQL database. I have found solutions in SQL code [1, 2], but I need something I can use directly in bash, I assume with the help of psql. In [2] there are also psql solutions, but I don't manage to adapt it in a IF statement.
I have tried this unsuccessfully (I am a PostgreSQL and bash newbie):
psql_USER=my
if [ "$( psql -h db -U postgres --no-psqlrc --single-transaction --pset=pager=off --tuples-only --set=ON_ERROR_STOP=1 -tc "SELECT 1 FROM pg_user WHERE usename = $psql_USER" | grep -q 1 )" == '1' ] > /dev/null 2> /dev/null; then
echo "HOURRA !"
fi;
Result is:
Password for user postgres:
ERROR: column « my » does not exist
LINE 1: SELECT 1 FROM pg_user WHERE usename = my
^
I would avoid the quoting problem like this:
if psql -Atq -c "SELECT '#' || usename || '#' FROM pg_user" | grep -q '#'"$psql_USER"'#'
then
echo yes
fi
The psql invocation selects a list of all usernames, prefixed and suffixed with #. The grep has return code 0 if psql_USER contains one of these user names, else 1. The then branch of if is only taken if the return code of the pipeline is 0, that is, if the user exists in the database.

bash scripting - function outputs results to empty file

I have a function that reads a list of names and uses that as input into another command like so:
runMain() {
getName=$(PGPASSWORD=$_clpw1 psql -h myendpoint.com -U ops_readonly -d dev -p 5439 -t -c "select datname from pg_database where datname not like 'template%' and datname not like 'tealium%' and datname not like 'padb%' and datname not like 'services%' and datname not like 'sales%' and datname not like 'dev%' and datname not like 'demo_%' and datname not like '%_demo' and datname not like 'nt_%';")
echo "${getName}" >> "${_file}"
for db in $(cat "${_file}");
do
getSchema=$(PGPASSWORD=$_clpw2 psql -h myendpoint.com -U masteruser -d "${db}" -p 5439 -t -c "select distinct 'GRANT SELECT ON ALL TABLES IN SCHEMA ' || table_schema ||' TO ops_readonly;' FROM information_schema.tables where table_catalog='%${db}%' and table_schema not in ('pg_catalog','information_schema');")
echo "${getSchema}" >> "${_script}"
done
}
I do an echo "${getSchema}" >> "${_script}" so I can output my query findings to a file.
I can see the output file has been touched and its like 32k, however the file its empty.
I've tested the command line string separately and it works and i'm getting the output I expect.
Is there a better way to capture the output to a file? What am I missing? Thank you.
Your file is empty (filled with 32k of blank lines) because ${getName} and ${getSchema} are both empty. Therefore, the problem is in those psql commands.
It's okay to put one level of double quotes inside a "$(…)" (never mind that SO's highlighting messes it up), so you can do this:
runMain() {
getName="$(PGPASSWORD="$_clpw1" psql -h myendpoint.com -U ops_readonly -d dev -p 5439 -t -c "select datname from pg_database where datname not like 'template%' and datname not like 'tealium%' and datname not like 'padb%' and datname not like 'services%' and datname not like 'sales%' and datname not like 'dev%' and datname not like 'demo_%' and datname not like '%_demo' and datname not like 'nt_%';" 2>&1)"
echo "${getName}" >> "${_file}"
for db in $(grep -o '[[:alnum:].-][[:alnum:].-]*' "${_file}");
do
getSchema="$(PGPASSWORD="$_clpw2" psql -h myendpoint.com -U masteruser -d "${db}" -p 5439 -t -c "select distinct 'GRANT SELECT ON ALL TABLES IN SCHEMA ' || table_schema ||' TO ops_readonly;' FROM information_schema.tables where table_catalog='%${db}%' and table_schema not in ('pg_catalog','information_schema');" 2>&1)"
echo "${getSchema}" >> "${_script}"
done
}
In addition to quoting your command substitution, I also quoted your passwords (which matters tremendously if you have spaces or other non-word characters in them!). Finally, I gave your commands 2>&1 to convert standard error into standard output so it can be captured in your output files, which might reveal other problems (like a bad password or bad connection).
The grep command makes the for loop safe to execute, ensuring the inputs are restricted to one or more alphanumeric characters, dots, and hyphens.
An output file was created. I was just getting 0 results. The root of my issue was in the plsql query. Running it via command line, my use of '%${db}%' is valid if running queries for postgres using like and since I am not using like, removing the surrounding % to '${db}' produced my output.

Hiera variables inside a puppet manifest script

I have Hiera running in combination with puppet/vagrant .
Say I have a puppet exec doing following
exec { create-project-database:
command => 'mysql -u root -e "CREATE DATABASE project_db DEFAULT CHARACTER SET = \'utf8\';"',
unless => 'mysql -u root information_schema -e "select * from information_schema.schemata;" | grep "project_db"',
require => Package[mysql-server],
}
I want to replace the string "project_db" with a variable I have inside my common.yaml of hiera
---
machine_message: 'This is the dev machine!'
codes_path: '/vagrant/code/laravel/'
project_db: 'project_db'
How I go about this? The syntax is not clear for me, and something like this brings errors:
command => 'mysql -u root -e "CREATE DATABASE 'hiera('project_db)' DEFAULT CHARACTER SET = \'utf8\';"',
You have an issue with your quotes in 'hiera('project_db)' (i.e. you're not closing before the parentheses so hiera('project_db') should be correct)
usually I do declare variables from hiera before so something like this
$DB_NAME = hiera('project_db')
exec { create-project-database:
command => "mysql -u root -e \"CREATE DATABASE $DB_NAME DEFAULT CHARACTER SET = 'utf8';\"",
unless => 'mysql -u root information_schema -e "select * from information_schema.schemata;" | grep "project_db"',
require => Package[mysql-server],
}

Bash script - cycle through MySQL records and update record based on user select

I'm a bit stuck, I need to loop through all of my mysql records and update one of the fields based on user input.
I was going to use the following to do the loop:
mysql -uuser -ppassword -s -e "SELECT company,product,category FROM MyTable"|while read varcomp varprod varcat;do
..and then use a PS3 select to provide the option:
PS3 "Please select new category: "
select opt1 in "Blocks" "Dolls" "Puzzles"
...before using, the following to update:
mysql -uuser -ppassword -s -e "UPDATE MyTable SET Category='$opt1' WHERE company='$varcomp' AND product='$varprod'"
...and then closing the loop.
I just can't seem to get all of the components to work together. I think it's because I'm using a while loop? Any advice on the best way to do this would be appreciated...
This should work:
while read -u 4 varcomp varprod varcat; do
echo "Company: $varcomp - Product: $varprod - Category: $varcat"
PS3="Please select new category: "
select opt in "Blocks" "Dolls" "Puzzles"; do
mysql -uuser -ppassword -s -e "UPDATE MyTable SET Category='$opt' WHERE company='$varcomp' AND product='$varprod';"
break
done
done 4< <(mysql -uuser -ppassword -s --skip-column-names -e "SELECT company,product,category FROM MyTable;")
You had the right idea, but the issue with using select inside a while read loop is that select and read both read input from stdin, so select would read all the SQL lines instead of prompting for user input. Instead, we redirect the mysql results to file descriptor 4, then tell read to use that instead of stdin, while select still reads from stdin as usual.
Note that if a user puts in a different value for select (eg 8 instead of 1, 2 or 3) then $opt will be null. You could replace the lines inside the select with:
[[ -n "$opt" ]] && mysql -uuser -ppassword -s -e "UPDATE MyTable SET Category='$opt' WHERE company='$varcomp' AND product='$varprod';" && break
This will keep prompting the user for input until they give a valid response.

Create FUNCTION in PostgreSQL from a Bash script

I'm trying to create a FUNCTION in my Postgres database from a Bash script. Unfortunately, I cannot get it to work. This is my script:
#!/bin/bash
# Save Postgres command to $POSTGRES_CMD
read -d '' POSTGRES_CMD <<"EOF"
CREATE OR REPLACE FUNCTION truncate_tables(username IN VARCHAR) RETURNS void AS $$
DECLARE
statements CURSOR FOR
SELECT tablename FROM pg_tables
WHERE tableowner = username AND schemaname = 'public';
BEGIN
FOR stmt IN statements LOOP
EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ';';
END LOOP;
END;
$$
LANGUAGE plpgsql;
EOF
sudo su - postgres -c "psql -d postgres -U postgres -c \"${POSTGRES_CMD}\""
When I run the script, I get the following error:
ERROR: Syntax error at »20541«
LINE 1: ...N truncate_tables(username IN VARCHAR) RETURNS void AS 20541
So it seems like something is wrong with the $$? How can I create a FUNCTION like in my script in Postgres from a Bash script? Do I have to mask anything?
Edit:
The final, working script (also added create language if it's not registered yet):
#!/bin/bash
sudo su - postgres -c "psql -d postgres -U postgres" << 'EOF'
CREATE LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION truncate_tables(username IN VARCHAR) RETURNS void AS $$
DECLARE
statements CURSOR FOR
SELECT tablename FROM pg_tables
WHERE tableowner = 'username' AND schemaname = 'public';
BEGIN
FOR stmt IN statements LOOP
EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ';';
END LOOP;
END;
$$
LANGUAGE plpgsql;
The $$ is replaced by the process id you should escape the $$ thing like this \$\$ or even \\$\\$ as it is escaped two times
Use <<'EOF' to stop bash interpolating the here document.
addition:
You can avoid passing everything through "-c" by using:
sudo su - postgres -c "psql -d postgres -U postgres" <<'EOF'
...
EOF
as stdin should be preserved through sudo and su
if anyone is looking for a version where env variables are passed (DB_USERNAME, DB_SCHEMA) here is:
psql -U dev -d clinical_trial -h db -v ON_ERROR_STOP=1 << EOF
CREATE OR REPLACE FUNCTION truncate_tables() RETURNS void AS \$\$
DECLARE
statements CURSOR FOR
SELECT tablename FROM pg_tables
WHERE tableowner = '$DB_USERNAME' AND schemaname = '$DB_SCHEMA' AND tablename not like 'flyway%';
BEGIN
FOR stmt IN statements LOOP
EXECUTE 'TRUNCATE TABLE $DB_SCHEMA.' || quote_ident(stmt.tablename) || ';';
END LOOP;
END;
\$\$
LANGUAGE plpgsql;
EOF

Resources