Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/hg hooks over tcp #1416

Merged
merged 31 commits into from Nov 27, 2020
Merged

Feature/hg hooks over tcp #1416

merged 31 commits into from Nov 27, 2020

Conversation

sdorra
Copy link
Member

@sdorra sdorra commented Nov 10, 2020

Proposed changes

Implement mercurial hook callback over a separate tcp socket. This replaces the http based solution which is theoretically vulnerable to manipulated http request, see #1411 for more details.

The change is tested with https://github.com/scm-manager/hg-server-spec

Your checklist for this pull request

  • PR is well described
  • Related issues linked to PR if existing and labels set
  • Target branch is not master (in most cases develop should bet the target of choice)
  • Code does not conflict with target branch
  • New code is covered with unit tests
  • CHANGELOG.md updated
  • Definition of Done's fulfilled: DoD // UI DoD
  • Documentation updated (only necessary for new features or changed behaviour)

Checklist for branch merge request (not required for forks)

@sdorra sdorra added bug Something isn't working enhancement New feature or request labels Nov 10, 2020
@eheimbuch eheimbuch self-assigned this Nov 17, 2020
try {
hookHandler.run();
} finally {
ThreadContext.unbindSubject();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would you unbind the subject here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This because the hook handler does a login, which binds a subject to the thread. We use a cached thread pool, which reuse existing threads and to avoid that a subject is used from a prior hook, we have to unbind it.

/** Field description */
public static final Set<Feature> FEATURES =
EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
public static final Set<Feature> FEATURES = EnumSet.of(Feature.COMBINED_DEFAULT_BRANCH);
Copy link
Member

@eheimbuch eheimbuch Nov 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this field protected like SonarCloud suggests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why SonarQube thinks that this should be protected, because FEATURES is used by HgRepositoryHandler. Changing FEATURES to protected leads to a compiler error.

@sdorra
Copy link
Member Author

sdorra commented Nov 18, 2020

The failing build seams to come from the jest upgrade storybookjs/storybook#10942

@pfeuffer
Copy link
Member

The message pipeline seems broken with these changes. You can check this easily with the commit message checker and an arbitrary failing regex check. The corresponding error does not reach the client (neither cli nor internal editor)

@sdorra
Copy link
Member Author

sdorra commented Nov 18, 2020

The message pipeline seems broken with these changes. You can check this easily with the commit message checker and an arbitrary failing regex check. The corresponding error does not reach the client (neither cli nor internal editor)

Okay, i'll take a look at this.

@sdorra
Copy link
Member Author

sdorra commented Nov 18, 2020

The failing build seams to come from the jest upgrade storybookjs/storybook#10942

I was able to fix the jest tests.

@pfeuffer
Copy link
Member

May it be possible to "transmit" the transaction id to the hook handler, so that the logs can be assigned to the correct request?

Comment on lines 109 to 111
} catch (Exception ex) {
LOG.warn("unknown error on hook occurred", ex);
return error("unknown error");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This catch block fetches all Exceptions from plugins, like for example the commit message checker. I don't think that a warn log is appropriate for these cases. On the other hand exceptions that are not thrown on purpose by plugins can indicate a problem. Problem is: I don't know a way how to differentiate between them. The PathWP plugin for example uses a simple exception that derives from RuntimeException. So we cannot rely on the usage of ExceptionWithContext.
On second thought, I think would do this:

Suggested change
} catch (Exception ex) {
LOG.warn("unknown error on hook occurred", ex);
return error("unknown error");
} catch (ExceptioWithContextn ex) {
LOG.debug("scm exception on hook occurred", ex);
return error(ex.getMessage());
} catch (Exception ex) {
LOG.warn("unknown error on hook occurred", ex);
return error("unknown error");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've applied your suggestion, but i've passed the message of the exception instead of "unkown error". This restores the behaviour before that change, but i'm not sure if this is the best way. Because we could leak technical details on unwanted exceptions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The leak is exactly why i've left it the way you've done it before for unknown exceptions. If you want to have proper messages, one should use ExceptioWithContext.

@sdorra
Copy link
Member Author

sdorra commented Nov 19, 2020

May it be possible to "transmit" the transaction id to the hook handler, so that the logs can be assigned to the correct request?

Done

@pfeuffer
Copy link
Member

pfeuffer commented Nov 20, 2020

Ok, don't know exactly how I got here, but I try to recap:

  1. For a hg repository I configured the message checker plugin only to accept special commit messages
  2. Using the editor plugin, I changed a file in this repo and tried to commit it with an invalid message
    • I got the correct error message
  3. I tried to commit again (simply pressing the button again, and the server hung up
  4. I restarted the server
  5. Again, I changed the file using the editor plugin and tried to commit
    • this time I got a generic error message and the log said that an exception was mapped to 500
    • I could click the button again and got the same result
  6. I cloned this repo in the shell
  7. ... changed a file and committed it
  8. On push I got a generic error.
  9. I disabled the commit message check and
  10. pushed again

This is what I got now:

2020-11-20 15:25:16.006 [qtp1006565608-94] [BpSGtKXdRg] DEBUG sonia.scm.web.cgi.DefaultCGIExecutor - execute cgi: python /home/rene/scm-home/lib/python/hgweb.py
2020-11-20 15:25:16.135 [cgi-pool-1] [          ] WARN  sonia.scm.web.cgi.DefaultCGIExecutor - Traceback (most recent call last):
  File "/home/rene/scm-home/lib/python/hgweb.py", line 54, in <module>
    wsgicgi.launch(application)
  File "/usr/lib/python2.7/dist-packages/mercurial/hgweb/wsgicgi.py", line 89, in launch
    for chunk in content:
  File "/usr/lib/python2.7/dist-packages/mercurial/hgweb/hgweb_mod.py", line 326, in run_wsgi
    for r in self._runwsgi(req, res, repo):
  File "/usr/lib/python2.7/dist-packages/mercurial/hgweb/hgweb_mod.py", line 353, in _runwsgi
    rctx, req, res, self.check_perm
  File "/usr/lib/python2.7/dist-packages/mercurial/wireprotoserver.py", line 233, in handlewsgirequest
    _callhttp(repo, req, res, proto, cmd)
  File "/usr/lib/python2.7/dist-packages/mercurial/wireprotoserver.py", line 469, in _callhttp
    rsp = wireprotov1server.dispatch(repo, proto, cmd)
  File "/usr/lib/python2.7/dist-packages/mercurial/wireprotov1server.py", line 82, in dispatch
    return func(repo, proto, *args)
  File "/usr/lib/python2.7/dist-packages/mercurial/wireprotov1server.py", line 664, in unbundle
    repo, gen, their_heads, b'serve', proto.client()
  File "/usr/lib/python2.7/dist-packages/mercurial/exchange.py", line 2773, in unbundle
    gettransaction()
  File "/usr/lib/python2.7/dist-packages/mercurial/exchange.py", line 2762, in gettransaction
    lockandtr[2] = repo.transaction(source)
  File "/usr/lib/python2.7/dist-packages/mercurial/localrepo.py", line 2058, in transaction
    hint=_(b"run 'hg recover' to clean up transaction"),
mercurial.error.RepoError: abandoned transaction found

2020-11-20 15:25:16.135 [qtp1006565608-94] [BpSGtKXdRg] WARN  sonia.scm.web.HgCGIExceptionHandler - Mercurial/Python process ends with return code 1

And here is the generic 500 exception:

2020-11-20 15:34:57.984 [qtp1859390802-86] [FpSGtMyx7G] TRACE sonia.scm.api.rest.ContextualExceptionMapper - map ExceptionWithContext to status code 500
sonia.scm.repository.spi.IntegrateChangesFromWorkdirException: errors from hook
	at sonia.scm.repository.spi.IntegrateChangesFromWorkdirException$MessageExtractor.forMessage(IntegrateChangesFromWorkdirException.java:75)
	at sonia.scm.repository.spi.HgModifyCommand.pullModifyChangesToCentralRepository(HgModifyCommand.java:136)
	at sonia.scm.repository.spi.HgModifyCommand.execute(HgModifyCommand.java:116)
	at sonia.scm.repository.api.ModifyCommandBuilder.execute(ModifyCommandBuilder.java:135)
	at com.cloudogu.scm.editor.EditorService$FileUploader.done(EditorService.java:151)
	at com.cloudogu.scm.editor.EditorResource.processFiles(EditorResource.java:591)
	at com.cloudogu.scm.editor.EditorResource.modify(EditorResource.java:501)
	at com.cloudogu.scm.editor.EditorResource.modifyInRoot(EditorResource.java:357)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
	at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:643)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:507)
	at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:457)
	at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:459)

I could reproduce this in a second repository: On the first change, I got the correct error message, the following commits hung up, after a restart I got generic exceptions due to the stack trace above.

@sdorra
Copy link
Member Author

sdorra commented Nov 22, 2020

I could reproduce this in a second repository: On the first change, I got the correct error message, the following commits hung up, after a restart I got generic exceptions due to the stack trace above.

The problem was introduced with the passthrough of the transaction id. However i've modified the hook socket protocol to be more robust, because the former error has lead to an endless loop in the python process. With the new implementation this should never happen again.

@pfeuffer
Copy link
Member

I could reproduce this in a second repository: On the first change, I got the correct error message, the following commits hung up, after a restart I got generic exceptions due to the stack trace above.

The problem was introduced with the passthrough of the transaction id. However i've modified the hook socket protocol to be more robust, because the former error has lead to an endless loop in the python process. With the new implementation this should never happen again.

Okay, now the process does not hang up, but again the error messages (for example from the custom message validator) are not propagated.

@sdorra
Copy link
Member Author

sdorra commented Nov 23, 2020

I can't confirm this. I get the custom error messages. How did you test this?

Screenshot 2020-11-23 at 12 25 28

Screenshot 2020-11-23 at 12 23 38

@pfeuffer
Copy link
Member

This is interesting. Indeed I get the correct error messaage with a repository I've not used for testing this before, but not with the one I used for the last test. Is the latter one broken somehow?

@sdorra
Copy link
Member Author

sdorra commented Nov 23, 2020

It is possible, that the old repository had a non finished transaction (abort: abandoned transaction found). This could happen with the prior version, when the pre receive hook hangs. I think it is possible to fix the abandoned transaction manually with hg recover.

@pfeuffer
Copy link
Member

It is possible, that the old repository had a non finished transaction (abort: abandoned transaction found). This could happen with the prior version, when the pre receive hook hangs. I think it is possible to fix the abandoned transaction manually with hg recover.

Indeed this fixed this issue, thanks! We'll have a look at the code later and get back to you ;-)

Use waitFor instead of exitValue to wait for process to finish
@sonarcloud
Copy link

sonarcloud bot commented Nov 27, 2020

Kudos, SonarCloud Quality Gate passed!

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 1 Code Smell

66.1% 66.1% Coverage
0.0% 0.0% Duplication

@pfeuffer pfeuffer merged commit 4e326fe into develop Nov 27, 2020
@pfeuffer pfeuffer deleted the feature/hg_hooks_over_tcp branch November 27, 2020 12:12
eheimbuch pushed a commit that referenced this pull request Feb 9, 2021
Fixes a regression which was introduced with #1416. In #1416 we have reimplemented the way configuration is passed to the mercurial cgi handler. Before #1416 we used environment variables which are picked up by hgweb.py, after #1416 we pass mercurial configurations as command line parameters directly in the HgCGIServlet. But sadly the configuration option for httppostargs uses still an environment variable, which is not picked up by anyone.

See #1525
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants