RHEL Engineering is moving the tracking of its product development work on RHEL 6 through RHEL 9 to Red Hat Jira (issues.redhat.com). If you're a Red Hat customer, please continue to file support cases via the Red Hat customer portal. If you're not, please head to the "RHEL project" in Red Hat Jira and file new tickets here. Individual Bugzilla bugs in the statuses "NEW", "ASSIGNED", and "POST" are being migrated throughout September 2023. Bugs of Red Hat partners with an assigned Engineering Partner Manager (EPM) are migrated in late September as per pre-agreed dates. Bugs against components "kernel", "kernel-rt", and "kpatch" are only migrated if still in "NEW" or "ASSIGNED". If you cannot log in to RH Jira, please consult article #7032570. That failing, please send an e-mail to the RH Jira admins at rh-issues@redhat.com to troubleshoot your issue as a user management inquiry. The email creates a ServiceNow ticket with Red Hat. Individual Bugzilla bugs that are migrated will be moved to status "CLOSED", resolution "MIGRATED", and set with "MigratedToJIRA" in "Keywords". The link to the successor Jira issue will be found under "Links", have a little "two-footprint" icon next to it, and direct you to the "RHEL project" in Red Hat Jira (issue links are of type "https://issues.redhat.com/browse/RHEL-XXXX", where "X" is a digit). This same link will be available in a blue banner at the top of the page informing you that that bug has been migrated.
Bug 1334903 - [RFE] Improved hook system
Summary: [RFE] Improved hook system
Keywords:
Status: CLOSED ERRATA
Alias: None
Product: Red Hat Enterprise Linux 6
Classification: Red Hat
Component: preupgrade-assistant
Version: 6.8
Hardware: Unspecified
OS: Unspecified
unspecified
unspecified
Target Milestone: rc
: ---
Assignee: Petr Hracek
QA Contact: Alois Mahdal
URL:
Whiteboard:
Depends On:
Blocks: 1335121
TreeView+ depends on / blocked
 
Reported: 2016-05-10 18:42 UTC by Alois Mahdal
Modified: 2016-11-04 08:57 UTC (History)
8 users (show)

Fixed In Version: preupgrade-assistant-2.1.10-1.el6
Doc Type: Enhancement
Doc Text:
The hook script has been updated to provide an API for modules.
Clone Of:
Environment:
Last Closed: 2016-11-04 08:57:28 UTC
Target Upstream Version:
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
Red Hat Product Errata RHBA-2016:2616 0 normal SHIPPED_LIVE preupgrade-assistant bug fix and enhancement update 2016-11-03 16:23:38 UTC

Description Alois Mahdal 2016-05-10 18:42:21 UTC
Description of problem
======================

Currently, hook script (pre-upgrade, post-upgrade...) system is rather
arbitrary:

 *  Deployment is done by modules copying scripts around to a (un)known
    location -- this means modules can overwrite each others'
    deployments,

 *  running is governed by r-u-t which just fires all scripts that it
    finds, without any additional debugging/tracking info.

This also makes it hard to debug problems: eg. from upgrade.log one does not
know which hooks were run and in which order, also outputs from several
scripts are sort of "merged" together.


Proposal
========


Deployment
----------

preupgrade-assistant should provide API for modules to deploy their hooks
safely.  This can be done by providing the target path dynamically for
*each module*.


Running
-------

For each event, have r-u-t start **only single** hook-runner script
provided by preupgrade-assistant that will do the job of firing all
hooks as deployed by modules.

Since now this hook runner is in control, it can provide all sorts of
features:

 *  logging start/stop of script,
 *  providing timestamps and/or prefixes,
 *  handling errors gracefully.


Examples
========


Deployment
----------

For example, a module called `xccdf_foo` calls:

    deploy_hook postupgrade myhook.sh

which, in the background, will actually do something like:

    cp myhook.sh /root/preupgrade/hooks/xccdf_foo/postupgrade/run_hook

Another example: module `xccdf_bar` needs to deploy other files as well:

    deploy_hook preupgrade myhook.sh myfile myfolder

while behind the scenes:

    cp myhook.sh          /root/preupgrade/hooks/xccdf_bar/postupgrade/run_hook
    cp -r myfile myfolder /root/preupgrade/hooks/xccdf_bar/postupgrade


This will eventually lead to a folder tree like

    /root
    └── preupgrade
        └── hooks
            ├── xccdf_bar
            │   └── preupgrade
            │       ├── myfile
            │       ├── myfolder
            │       │   ├── some
            │       │   └── files
            │       └── run_hook
            ├── xccdf_baz
            │   └── postupgrade
            │       └── run_hook
            └── xccdf_foo
                └── postupgrade
                    └── run_hook


Running
-------

Having a tree such as above, all that the runner needs to do in
a hook-able event (say, "postupgrade"):

    find /root/preupgrade/hooks -path '*/postupgrade/run_hook' \
      | while read path;
        do
            log "hook-runner: starting: $path"
            pushd "${path%/*}" >/dev/null
                chmod +x run_hook
                ./run_hook; es=$?
            popd >/dev/null
            log "hook-runner: exited with status $es: $path"
        done

Comment 3 Petr Hracek 2016-06-06 13:42:33 UTC
This is implemented by upstream PR https://github.com/phracek/preupgrade-assistant/pull/108

Comment 6 Alois Mahdal 2016-08-19 18:17:37 UTC
This is not implemented fully:

 *  preupgrade hooks are ignored completely,

 *  second file/dir is ignored eg.

        deploy_hook postupgrade foo bar

    saves only `foo` (as run_hook), but ignores `bar`

Also, the directory naming is a bit strange:

    /root/preupgrade/hooks/xccdf_api_deploy_hook

Directory is named `xccdf_api_deploy_hook`, but that is not the module's XCCDF name but some pseudo-version of it.  This makes it harder to do reliable automated test, since one has to "guess" the module folder instead of just kowing it.

Comment 7 Alois Mahdal 2016-08-19 21:08:35 UTC
Also there's no error handling;  I can provide

 *  no second argument (script name)
 *  non-existent file or directory
 *  or directory where script is expected
 *  unsupported hook type

and the function just silently passes.

Comment 8 Petr Hracek 2016-08-22 12:43:59 UTC
Preupgrade-assistant sends to a module value called XCCDF_VALUE_MODULE_NAME wich is used for deploying. It seems, like you modules is called api_deploy_hook which is correct. Basically, it is a modules name in your directory tree.

You note about bar is not copied is correct.
But as Bash as Python should have the same syntax.
It means:
    deploy_hook postupgrade myhook.sh
    cp myhook.sh /root/preupgrade/hooks/xccdf_foo/postupgrade/run_hook

This works.

This part is a pretty problematic
    deploy_hook preupgrade myhook.sh myfile myfolder

Nowadays, I don't have an idea how to do it from python code.

It needs a time for analysis.

Comment 9 Alois Mahdal 2016-08-23 04:16:11 UTC
(In reply to Petr Hracek from comment #8)
> Preupgrade-assistant sends to a module value called XCCDF_VALUE_MODULE_NAME
> wich is used for deploying.  It seems, like you modules is called
> api_deploy_hook which is correct.  Basically, it is a modules name in your
> directory tree.

No, it's not.

 *  XCCDF_VALUE_MODULE_NAME         is 'api_deploy_hook'
 *  rule id (only one seen in HTML) is 'xccdf_preupg_rule_api_deploy_hook_check
 *  folder name                     is 'xccdf_api_deploy_hook'

Which is bad for testing as we have to know both names ("rule id" to reach into XML/HTML and "folder name" to reach for hook artifacts in logs) for every single pre/post upgrade hook test we ever write.

Ideally there should be only one way to name a module, so why not use rule id?  It's already used in XML, HTML reports and preupg --list-rules, so I think it's only consistent to use it here as well.

In fact, when I saw in your code that you are prefixing 'xccdf_', I figured that this is what you want to do (I still can't think of any other reason; I'm sure you did not intend to create a new mix-match name but you effectively did).


> You note about bar is not copied is correct.
> But as Bash as Python should have the same syntax.

I don't understand.  This has nothing to do with Python; I only tested Bash API so far and it's broken.

(Of course Bash and Python should have the same syntax.  Of course this should work the same way in both.)



> It means:
>     deploy_hook postupgrade myhook.sh
>     cp myhook.sh /root/preupgrade/hooks/xccdf_foo/postupgrade/run_hook
> 
> This works.

Yes, and it's just about the only case that works ;)  Any other use case I throw at it fails.


> This part is a pretty problematic
>     deploy_hook preupgrade myhook.sh myfile myfolder
> 
> Nowadays, I don't have an idea how to do it from python code.

I don't see why it should be problematic; it's nothing but copying couple of files and folders.  What about `shutil.copytree`?  Or even if you had to use `Popen`, I can't see what's the challenge here.

(Maybe I'll send PR if I manage to shove it into spacetime.)

Comment 15 Alois Mahdal 2016-09-09 06:34:30 UTC
Result from current version (preupgrade-assistant-2.1.9-5.el6), Bash API:

deploy_hook() does not deploy anything at all.  /preupgrade/hooks is always empty and following message gets logged:

    Module path is not specfied.

Comment 16 Alois Mahdal 2016-09-09 15:56:57 UTC
I can't test Python API, but from the code:

    try:
        MODULE_PATH = os.environ['XCCDF_VALUE_MODULE_PATH']
    except KeyError:
        MODULE_PATH = "MODULE_PATH"

...

        hook_dir = "%s/hooks/%s/%s" % (VALUE_TMP_PREUPGRADE, MODULE_PATH, deploy_name)

I'm assuming the same scenario is not possible.  Instead:

 *   if XCCDF_VALUE_MODULE_PATH hasn't been set, string literal 'MODULE_PATH'
     is used, which means that hooks of the same types will get overwritten
     by each other under eg. `hooks/MODULE_PATH/preupgrade`

 *   if XCCDF_VALUE_MODULE_PATH was set to empty string (or any other string
     not specific for a module), same thing happens, just under eg.
    `hooks//preupgrade`.

Comment 17 Alois Mahdal 2016-09-09 16:08:48 UTC
Another issue from code review:

Neither Bash nor Python API take into account possibility of the same module calling the function twice.  In both cases, the function will try to overwrite the previous content of the hook dir, possibly merging directories in bad way.

Now the question is; should we support more hooks per module?  I think we can get away with not supporting that; it's better to avoid need to solve the naming conflict (we would probably have to number the directories or something) and if someone really needs to launch N scripts, they can always launch them from one hook.

So, possibilities if the taeget dir is already 'occupied' (ie. hook_dir already exists):

 *  (a) always throw error and don't copy anything,

 *  (b) always rm -rf hook_dir silently,

 *  (c) always rm -rf hook_dir silently but warn that the previous hook has been
    removed.

Whatever we do, we need to document that; if we do state it clearly, we can get away with (b) which is the simplest (you don't even need `if`; just `rm -rf "$hook_dir"; mkdir -p "$hook_dir"`).

Comment 18 Alois Mahdal 2016-09-09 16:10:40 UTC
Also, in Bash variable references are not quoted properly.  Arguments with spaces (or starting with '-') will break the whole thing.

Comment 19 Petr Hracek 2016-09-12 11:45:53 UTC
(In reply to Alois Mahdal from comment #15)
> Result from current version (preupgrade-assistant-2.1.9-5.el6), Bash API:
> 
> deploy_hook() does not deploy anything at all.  /preupgrade/hooks is always
> empty and following message gets logged:
> 
>     Module path is not specfied.

MODULE_PATH is specified by XML file. all-xccdf.xml file provides this information.

Comment 20 Petr Hracek 2016-09-12 11:51:50 UTC
(In reply to Alois Mahdal from comment #17)
> Another issue from code review:
> 
> Neither Bash nor Python API take into account possibility of the same module
> calling the function twice.  In both cases, the function will try to
> overwrite the previous content of the hook dir, possibly merging directories
> in bad way.
> 
I don't see any benefit, why it should be called twice. 
I would prefer to call hook and exit.

> Now the question is; should we support more hooks per module?  I think we
> can get away with not supporting that; it's better to avoid need to solve
> the naming conflict (we would probably have to number the directories or
> something) and if someone really needs to launch N scripts, they can always
> launch them from one hook.

No please do not complicate it. I would like to support on hook per module.

> 
> So, possibilities if the taeget dir is already 'occupied' (ie. hook_dir
> already exists):

This should not happened at all. Full directory in "hook_system" is identified by MODULE_PATH variable
which is specified in all-xccdf.xml file.

> 
>  *  (a) always throw error and don't copy anything,
> 
>  *  (b) always rm -rf hook_dir silently,
> 
>  *  (c) always rm -rf hook_dir silently but warn that the previous hook has
> been
>     removed.
> 
> Whatever we do, we need to document that; if we do state it clearly, we can
> get away with (b) which is the simplest (you don't even need `if`; just `rm
> -rf "$hook_dir"; mkdir -p "$hook_dir"`).

Comment 21 Alois Mahdal 2016-09-12 12:28:55 UTC
(In reply to Petr Hracek from comment #20)
> (In reply to Alois Mahdal from comment #17)
> > Another issue from code review:
> > 
> > Neither Bash nor Python API take into account possibility of the same module
> > calling the function twice.  In both cases, the function will try to
> > overwrite the previous content of the hook dir, possibly merging directories
> > in bad way.
> > 
> I don't see any benefit, why it should be called twice. 

Neither do I but it can happen; that's the point.


> > Now the question is; should we support more hooks per module?  I think we
> > can get away with not supporting that; it's better to avoid need to solve
> > the naming conflict (we would probably have to number the directories or
> > something) and if someone really needs to launch N scripts, they can always
> > launch them from one hook.
> 
> No please do not complicate it. I would like to support on hook per module.

Nice to know you agree with me ;)


> > So, possibilities if the taeget dir is already 'occupied' (ie. hook_dir
> > already exists):
> 
> This should not happened at all. Full directory in "hook_system" is
> identified by MODULE_PATH variable
> which is specified in all-xccdf.xml file.

As I said above, if someone calls the function once and then again, we have to deal with it.

It's not a question of what should and what should not happen; we have to define behavior for all (usual, at least) cases.

Comment 25 Alois Mahdal 2016-09-15 04:12:34 UTC
Well, it's better but still not complete:

 *  No quoting of arguments:  arguments with spaces or other special characters
    will break it.

 *  Non-existent files or dirs (except the hook scripts) are ignored.

 *  It's not clear on which error conditions the function should just print
    error and when it should exit_error.

    I would expect that any error conditions should "crash" the module,
    but exit_error is only called in two cases (double call or bad type).

I've sent PR to Github that attempts to solve all above issues.

~

Also, after reviewing Python function, I believe it's not capable of copying files if they are specified by absolute path:

    if os.path.isdir(arg):
        shutil.copytree(os.path.join(VALUE_CURRENT_DIRECTORY, arg),
                        os.path.join(hook_dir))
    else:
        shutil.copyfile(os.path.join(VALUE_CURRENT_DIRECTORY, arg),
                        os.path.join(hook_dir, arg))

I haven't tested it, but I believe this join will change meaning of an absolute path.

(Frankly, I have no idea why the os.path.join() is even used, although I admit I haven't studied shutil closely.)

Comment 27 Alois Mahdal 2016-09-15 17:13:30 UTC
Finally the Bash version of the function passes my test.

But still, in the Python version, I can see the problem with joining paths hasn't been solved.

Can you confirm it's OK and you have test coverage for that?

Comment 28 Petr Hracek 2016-09-16 12:11:27 UTC
This could be solved during next path.
In case of using absolute path then we will have a trouble. 
Test coverage will be add later on to upstream.

I would suggest to close it. We have a small set of modules which we can fix it during next release.

joining path works fine (tested) except absolute paths.

Comment 29 Alois Mahdal 2016-09-16 14:08:44 UTC
Wait, are you suggesting to postpone the whole RFE, now when it's 'almost' done (well, the Bash part *is* done).

That seems a bit harsh, given that most of it works.  If you're really unable to fix the issue with absolute path, I suggest just add a note to the doc and fix only the abspath issue in next cyle.

Given that no modules actually use this now, I think we could get away with it.

(But frankly, I don't see how that fix could be complicated, as I have pointed out several times, there's just no need for `os.path.join(VALUE_CURRENT...)`, just remove it and problem is gone ;).  I might send PR if I find out how to create a Python module and test this.)

Comment 30 Petr Hracek 2016-09-19 06:42:05 UTC
Of course not. This RFE has to be solved during this release cycle.

But absolute path can be solved either later on or only in upstream, co we can close this bugzilla.

Comment 31 Petr Hracek 2016-09-19 06:48:36 UTC
Or even better solution. Alone hook system is used by module itself.
And I don't see any reason why to copy a file from directory which is not related to module itself. E.g. /root, /var/ etc.

Therefore If argument is a file or directory like name with absolute path, then it has to finish with an error status like unsupported behavior.

Comment 33 Alois Mahdal 2016-09-21 21:23:29 UTC
Tested with preupgrade-assistant-2.1.10-1.el6.

Bash version works fine.

The Python version still has problems.

 *  every argument after 2nd one will cause deploy_hook emit an error

    filed as bug 1378205

    https://github.com/upgrades-migrations/preupgrade-assistant/pull/149

 *  argument parsing is broken; it will traceback if mandatory args are
    missing

    filed as bug 1378215

    https://github.com/upgrades-migrations/preupgrade-assistant/pull/150

 *  error message on bad hook type is malformed

    (not filed downstream)
    https://github.com/upgrades-migrations/preupgrade-assistant/pull/151

 *  hook files are deployed to incorrect path

    filed as bug 1378219

    https://github.com/upgrades-migrations/preupgrade-assistant/pull/152

Most of these don't look like blockers, so I'm closing this; if any of above need to be fixed, let's escalate the respective bugs.

(I've also tested with the PRs appied; all seems fine.)

Comment 35 errata-xmlrpc 2016-11-04 08:57:28 UTC
Since the problem described in this bug report should be
resolved in a recent advisory, it has been closed with a
resolution of ERRATA.

For information on the advisory, and where to find the updated
files, follow the link below.

If the solution does not work for you, open a new bug report.

https://rhn.redhat.com/errata/RHBA-2016-2616.html


Note You need to log in before you can comment on or make changes to this bug.