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

feat(docs): instructions on integrating HTML log with Jenkins #693

Merged
merged 11 commits into from
Dec 13, 2021
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Change log
==========

0.19.10 (2021-11-05)
-------------------
--------------------

New features
~~~~~~~~~~~~
Expand Down
4 changes: 4 additions & 0 deletions doc/args.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,9 @@ add comments on found issues right to the selected code review system.
| * -f='test 1:!unit test 1' - run all steps with 'test 1' substring in their names except those
containing 'unit test 1'

--html-log -hl : @after
To make sure all the interactive features of such a page work right in Jenkins artifacts,
please refer to the following :doc:`guide <jenkins>`

{init,run,poll,submit,github-handler} : @replace
| See detailed description of additional commands :doc:`here <additional_commands>`.
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Project 'Universum'
github_handler.rst
universum_docs.rst
teamcity.rst
jenkins.rst
examples.rst
changelog_ref.rst

Expand Down
70 changes: 70 additions & 0 deletions doc/jenkins.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Integration with Jenkins
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
========================

In `Jenkins` `Universum` is usually launched via single ``{python} -m universum`` command as one long step in a
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
single build stage. Because of that, the whole `Universum` log is printed as a plain text without navigation anchors.

.. admonition:: Compare with these:
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved

* When ran locally, all Universum build step logs are stored to separate files instead of printing their
output to console
* When launched on TeamCity, `Unviersum` uses `service messages
<https://www.jetbrains.com/help/teamcity/service-messages.html>`__ to increase log readability

To simplify integration and debug, we provide a user-friendly interactive log with collapsible blocks and
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
other features. This log is generated when `--html-log <args.html#Output>`__ option is passed to command line.
It can be accessed from project `artifacts <args.html#Artifact\ collection>`__.

k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
.. note::

By default, Jenkins `does not render interactive content <https://www.jenkins.io/doc/book/security/user-content/>`__.
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
This means that without changing server settings, interactive features of the generated log will be
inaccessible when opened directly from Jenkins artifacts.


Jenkins Content-Security-Policy
-------------------------------

A recommended way to allow Jenkins server to render interactive user content is to `configure Jenkins to publish
artifacts on a different domain <https://www.jenkins.io/doc/book/security/user-content/#resource-root-url>`__
by changing ``Resource Root URL`` in `Manage Jenkins » Configure System » Serve resource files from another domain`
from something like ``my.jenkins.io`` to ``res.my.jenkins.io``.
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved

Note that Jenkins interaction with resource domain, located on the same host is not done via ``localhost``
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
network interface. Jenkins treats ``another domain`` as an external URL when redirecting. This means that:
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved

1. Both ``my.jenkins.io`` and ``res.my.jenkins.io`` domain names must be resolved correctly
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
2. All headers must be passed correctly
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved

.. note::

If main server domain name is not resolved using DNS, ``/etc/hosts`` or any other means, the web-interface
will only be accessible via IP, and not the name. As resource domain might be located at the same IP,
the resolving of both names is crucial
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved

Here are the symptoms of domain names not resolving correctly:
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved

1. Jenkins warnings when trying to save the updated settings
2. Client inability to access said pages (timeout error)

A reason for headers not passed correctly can be Nginx configuration. Without specification Nginx replaces
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
``Host`` headers on redirection, that will lead to ``404 NOT FOUND`` errors when trying to access artifacts
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
from Jenkins. To pass them correctly, adjust the configuration accordingly (see
https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/#passing-request-headers for more details).

.. note::

Also you might need to set ``server_name`` to ``my.jenkins.io res.my.jenkins.io`` if they are located
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
on the same host


Further integration
-------------------

To integrate the generated log into `Jenkins` job, we suggest `htmlpublisher <https://plugins.jenkins.io/htmlpublisher/>`__
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
plugin. After installing it to your server, apply the following changes:

1. Add ``-hl``/``--html-log``/``-hl <name>`` option to the `Universum` command line
2. Pass the log name to plugin configuration as described in manual (https://plugins.jenkins.io/htmlpublisher/)
via `Post-build Actions` or pipeline
3. Let Jenkins render the interactive content of log (the section above)
k-dovgan marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 3 additions & 3 deletions tests/test_html_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ def browser():


def test_cli_log_custom_name(tmpdir):
custom_log_name = "custom_name"
custom_log_name = "custom_name.html"
artifact_dir = check_cli(tmpdir, ["-hl", custom_log_name])
assert os.path.exists(os.path.join(artifact_dir, f"{custom_log_name}.html"))
assert os.path.exists(os.path.join(artifact_dir, custom_log_name))


def test_cli_log_default_name(tmpdir):
Expand All @@ -90,7 +90,7 @@ def test_success_clean_build(tmpdir, browser):


def check_html_log(artifact_dir, browser):
log_path = os.path.join(artifact_dir, f"{log_name}.html")
log_path = os.path.join(artifact_dir, log_name)
assert os.path.exists(log_path)

browser.get(f"file://{log_path}")
Expand Down
4 changes: 2 additions & 2 deletions universum/modules/output/html_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

class HtmlOutput(BaseOutput):

default_name = "universum_log"
default_name = "universum_log.html"

def __init__(self, *args, log_name=default_name, **kwargs):
super().__init__(*args, **kwargs)
Expand All @@ -23,7 +23,7 @@ def __init__(self, *args, log_name=default_name, **kwargs):
self.module_dir = os.path.dirname(os.path.realpath(__file__))

def set_artifact_dir(self, artifact_dir):
self._log_path = os.path.join(artifact_dir, f"{self._log_name}.html")
self._log_path = os.path.join(artifact_dir, self._log_name)

def open_block(self, num_str, name):
opening_html = f'<input type="checkbox" id="{num_str}" class="hide"/>' + \
Expand Down
18 changes: 7 additions & 11 deletions universum/modules/output/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,20 @@ class Output(Module):
terminal_driver_factory = Dependency(TerminalBasedOutput)
html_driver_factory = Dependency(HtmlOutput)

html_log_disabled_arg_value = None

@staticmethod
def define_arguments(argument_parser):
parser = argument_parser.get_or_create_group("Output", "Log appearance parameters")
parser.add_argument("--out-type", "-ot", dest="type", choices=["tc", "term", "jenkins"],
help="Type of output to produce (tc - TeamCity, jenkins - Jenkins, term - terminal). "
"TeamCity and Jenkins environments are detected automatically when launched on build "
"agent.")
"TeamCity and Jenkins environments are detected automatically "
"when launched on build agent.")
# `universum` -> html_log == default
# `universum -hl` -> html_log == const
# `universum -hl custom` -> html_log == custom
parser.add_argument("--html-log", "-hl",
nargs="?", const=HtmlOutput.default_name, default=Output.html_log_disabled_arg_value,
help="Generate self-contained HTML log in artifacts directory. "
"You may specify a file name in this parameter's value or default "
"one will be used")
parser.add_argument("--html-log", "-hl", nargs="?", const=HtmlOutput.default_name, default=None,
help=f"Generate a self-contained user-friendly HTML log. "
f"Pass a desired log name as a parameter to this option, or a default "
f"'{HtmlOutput.default_name}' will be used.")

def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -103,13 +100,12 @@ def log_execution_finish(self, title: str, version: str) -> None:
self.html_driver.log_execution_finish(title, version)

def _create_html_driver(self):
is_enabled = self.settings.html_log != self.html_log_disabled_arg_value
is_enabled = self.settings.html_log is not None
html_driver = self.html_driver_factory(log_name=self.settings.html_log) if is_enabled else None
handler = HtmlDriverHandler(html_driver)
return handler



class HasOutput(Module):
out_factory: ClassVar = Dependency(Output)

Expand Down