Putting dependencies in place prior to running charm code

Hello,

Working on getting our charms to run on both centos and ubuntu, I have been facing the battle of getting python3 installed on centos prior to running any operator charm code (because we need python3 to execute the charm code itself). After trying a few different things that didn’t really fit, I got something working that, while I’m not super excited about its implementation, I’m excited that it gets the job done. Either way, I thought it was worth putting out there for feedback if nothing else.

What I did to get python3 and snapd installed on the centos box prior to running any charm code in our operator charm, was build the charm with only a single bash install hook in the hooks/ directory and no dispatch file. This basically tricks the charm into thinking that its running legacy hooks so it wont try to initiate the operator framework before the install hook is able to install python. Secondly, the bash install hook sets up the needed deps (python3, snapd), creates a dispatch file, regenerates the start and upgrade-charm hook symlinks and kicks off the operator framework. Writing out the dispatch file and linking in the new start and upgrade-charm hooks were the needed steps to be able to kick off the operator framework install event at the end.

#!/bin/sh -e
# This hook installs the centos dependencies needed to run the charm,
# creates the dispatch executable, regenerates the symlinks for start and
# upgrade-charm, and kicks off the operator framework.


# Source the os-release information into the env.
. /etc/os-release

# Determine if we are running in centos or ubuntu, if centos
# provision the needed prereqs.
if [ $ID = 'ubuntu' ]; then
    echo "Running ubuntu, prereqs installed."
else
    echo "Running centos, installing prereqs."
    yum --setopt=ip_resolve=4 -y install python3 snapd
    systemctl start snapd
    systemctl enable snapd
    systemctl enable --now snapd.socket
    if ! [ -e '/snap' ]; then
      ln -s /var/lib/snapd/snap /snap
    fi
fi

# Create the dispatch file and make it executable.
cat <<EOF >dispatch
#!/bin/sh
JUJU_DISPATCH_PATH="\${JUJU_DISPATCH_PATH:-\$0}" PYTHONPATH=lib:venv ./src/charm.py
EOF
chmod +x dispatch

# Remove the pre-generated hooks.
rm -rf hooks/start
rm -rf hooks/upgrade-charm
# Symlink in the dispatch hooks for start and upgrade charm.
ln -s dispatch hooks/start
ln -s dispatch hooks/upgrade-charm

# Invoke the operator framework install event.
JUJU_DISPATCH_PATH="hooks/install" PYTHONPATH=lib:venv ./src/charm.py

Possibly there is a better way to accomplish this sort of task?

Thanks

2 Likes

Which file is this dispatch and can you describe the sequence of which all the different scripts are invoked because the process here is valuable to know about.

Why not make dispatch a bash file that does what you need and then invokes src/charm.py? Eg something like:

#!/bin/bash

if ! type -a python3; then
    yum --setopt=ip_resolve=4 -y install python3 snapd
    systemctl start snapd
    systemctl enable snapd
    systemctl enable --now snapd.socketfi
    if ! [ -e '/snap' ]; then
      ln -s /var/lib/snapd/snap /snap
    fi
fi
exec src/charm.py

@jameinel thanks. This is what I was initially trying to do, but I was hitting some issues. Let me see if I can repro.

It’s the file named “dispatch” in the script^

In Juju 2.8 support for a new script in charms was added. You can create an executable script at the top level of your charm called “dispatch” instead of implementing lots of scripts inside hooks/.
That script will be invoked with the environment variable JUJU_DISPATCH_PATH set for each hook that juju wants to invoke, and then it is up to your dispatch code to decide if hooks need to be run.
Note that if ‘dispatch’ exists, then none of the scripts in hooks/
are run by Juju, it is the responsibility of ‘dispatch’ to do that work.
The operator framework has code to look for any scripts in hooks/* or actions/* based on JUJU_DISPATCH_PATH and will exec them before running any other handlers inside the rest of the python code.

1 Like

@hallback you need to see this ^

This should be added to the documentation really as this is a secret functionality?

Yes, absolutely. It’s not intended to be secret, but was implemented to support the Operator Framework and isn’t currently documented.

1 Like

This is an important line. I assume that without exec, argv would get messed up.

@jameinel If I abandon my method used above for your’s I get this

2020-07-22 19:57:14 DEBUG juju.worker.uniter agent.go:20 [AGENT-STATUS] executing: running install hook
2020-07-22 19:57:14 DEBUG juju.worker.uniter.runner runner.go:715 starting jujuc server  {unix @/var/lib/juju/agents/unit-slurmd-34/agent.socket <nil>}
2020-07-22 19:57:14 DEBUG install /usr/bin/env: python3: No such file or directory
2020-07-22 19:57:14 ERROR juju.worker.uniter.operation runhook.go:136 hook "install" (via hook dispatching script: dispatch) failed: exit status 127

This is why I had to create my workaround above.

My wording was of course meant to be a bit witty, I totally understand that the docs would follow. This functionality is i great to know about.

/usr/bin/env: python3: No such file or directory

I believe many places don’t actually have ‘/usr/bin/env’ so your #! in src/charm.py is wrong.
You can also be more explicit with
exec python3 src/charm.py

Rather than expecting the #! to be correct.

@jameinel while I agree with you that we shouldn’t expect /usr/bin/env to exist, if I run juju add-machine --series centos7 and ssh into the box when it comes up /usr/bin/env exists.

$ type -a /usr/bin/env
/usr/bin/env is /usr/bin/env
[ubuntu@n-c132 ~]$ /usr/bin/env python3
/usr/bin/env: python3: No such file or directory
[ubuntu@n-c132 ~]$ type -a python3
-bash: type: python3: not found

I’m thinking the best solution in this case would be to symlink the hooks to charm.py and forfeit using dispatch and charmcraft for this charm.

So the actual failure here is that python3 still doesn’t exist. It may be that my ‘if type’ bash-fu wasn’t sufficient to detect the lack of python3 and cause it to be installed.
But if it is telling you that /usr/bin/env does exist but python3 doesn’t then that is what you need to fix.
Is it called python3 on CentOS? I know on Windows it is just called ‘python’.

We could introspect the output of python -V perhaps

@jameinel Correct, python3 does not exist by default on centos. The drive behind this is to get python3 installed on centos in order to run the charm/operator code. To do this, I thought I could install python3 in the install hook and then call the operator framework after installing python3. The problem I faced, is that if the dispatch file exists before the install hook runs, I get the /usr/bin/env: python3: No such file or directory error. If I write out the dispatch file in the install hook and then call it after installing python3 at the bottom of the bash file I’m able to get things working. I’ve identified another workaround that avoids using dispatch altogether, but I would like to figure out a way to install dependencies needed to run the operator framework from the charms perspective and use dispatch.

I thought the goal was to put the “make sure python3 is installed” into a quick test inside dispatch and then installing it if it doesn’t exist, which would cover all hooks that are run from there, rather than having an install that then creates a dispatch, etc.

@jameinel yeah, it’s weird, that is definitely the goal. What I’m trying to get at here is that the goal was to put the "make sure python3 is installed” into a quick test inside dispatch and then installing it if it doesn’t exist, which would cover all hooks that are run from there doesn’t work if the dispatch file pre-exists - which is why I have to do my hack