Repository: kedro-org/kedro
Pull request: Fix/the `getpass.getuser()` call fails when no user is present
## Description

Fixes #1624.

## Development notes

We now wrap the call in a `try-except` block to prevent the failure.

## Checklist

- [x] Read the [contributing](https://github.com/kedro-org/kedro/blob/main/CONTRIBUTING.md) guidelines
- [ ] Opened this PR as a 'Draft Pull Request' if it is work-in-progress
- [ ] Updated the documentation to reflect the code changes
- [x] Added a description of this change in the [`RELEASE.md`](https://github.com/kedro-org/kedro/blob/main/RELEASE.md) file
- [x] Added tests to cover my changes


<a href="https://gitpod.io/#https://github.com/kedro-org/kedro/pull/1642"><img src="https://gitpod.io/button/open-in-gitpod.svg"/></a>



Problem statement:
KedroSession: getpass.getuser() call fails
## Description
First of all, I want to thank you for this splendid work that is Kedro. I believe it makes our life easier and I am glad that we have adopted it at my company.
This is the first time that I post an issue here, so I hope to frame this correctly!

In my company, we are using Kedro pipelines orchestrated in Airflow (we don't use kedro-airflow but we have a custom setup).
I have recently upgraded one pipeline to Kedro 0.18, but it's currently failing here:

```
# Line 167 of kedro/framework/session/session.py
session_data["username"] = getpass.getuser()
```

with the following error:
``KeyError: 'getpwuid(): uid not found: 1000``

I believe this is due to this process running with an uid that is not defined in /etc/passwd.
I am not super familiar with this things, but googling, it seems this is not an uncommon situation when using existing images.

Maybe it might be better to wrap that line in a try/except clause and catch ImportErrors?
This username tag does not seem to serve a critical purpose so I think the code should not crash because of this.

Right now I have solved it temporarily by setting an environment variable (USERNAME). What do you think?


## Steps to Reproduce
1. Run Kedro pipeline in a containerized setting with a UID that does not exist in /etc/passwd

## Expected Result
I expect that the code runs fine, and maybe the KedroSession is initialized with a "none" user or something like that

## Actual Result
The code crashes with a KeyError when setting username in KedroSession

## Your Environment
Our custom Docker Image is ultimately based on python:3.8-buster:


* Kedro version used (`pip show kedro` or `kedro -V`): 0.18.1
* Python version used (`python -V`): 3.8
* Operating system and version: Code running in Airflow on a custom image based on python:3.8-buster

Hints from issue discussion:
Hello @naikio and thank you for your comments! Very pleased to hear that kedro makes your life easier 🙂 Do you mind me asking what your company is?

Thanks for the very clear bug report. You're right that that this `username` isn't currently used anywhere, and your current workaround of setting `USERNAME` in an environment variable is definitely a good one.

The username destined to appear on the experiment tracking screen in kedro-viz but doesn't seem to have quite made it there yet (see recently opened https://github.com/kedro-org/kedro-viz/issues/911). I was actually considering adding `getpass.getuser` to our code so I'm very surprised to see that it's already there! Your bug report is remarkably timely 😀 

I'm actually also surprised that that this is the first time we've heard about this problem since this line of code will have been executed A LOT on lots of different machines (it's run every time anyone does a `kedro run`). I'm not a devops person but I would be curious to know if your situation of not having uid defined in `/etc/passwd` is therefore quite unusual or not quite right in some way, even if it's a problem you've seen discussed in other contexts online?

Either way, you're absolutely right that kedro should not crash in such a scenario and we should wrap the call in a `try/except`. If you get a chance please feel free to raise a PR that does this - otherwise I'll add it to our backlog.

Commit message: Lint and improve test

Signed-off-by: Antony Milne <antony.milne@quantumblack.com>

Patch to review:
diff --git a/RELEASE.md b/RELEASE.md
index 57ec4d926a..511baae0ce 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -29,6 +29,7 @@
 * Bumped the upper bound for the Flake8 dependency to <5.0.
 * `kedro jupyter notebook/lab` no longer reuses a Jupyter kernel.
 * Required `cookiecutter>=2.1.1` to address a [known command injection vulnerability](https://security.snyk.io/vuln/SNYK-PYTHON-COOKIECUTTER-2414281).
+* The session store no longer fails if a username cannot be found with `getpass.getuser`.
 
 
 ## Upcoming deprecations for Kedro 0.19.0
diff --git a/kedro/framework/session/session.py b/kedro/framework/session/session.py
index eca3c00fed..3b93b7ff1e 100644
--- a/kedro/framework/session/session.py
+++ b/kedro/framework/session/session.py
@@ -163,7 +163,12 @@ def create(  # pylint: disable=too-many-arguments
         if extra_params:
             session_data["extra_params"] = extra_params
 
-        session_data["username"] = getpass.getuser()
+        try:
+            session_data["username"] = getpass.getuser()
+        except Exception as exc:  # pylint: disable=broad-except
+            logging.getLogger(__name__).debug(
+                "Unable to get username. Full exception: %s", exc
+            )
 
         session._store.update(session_data)
 
diff --git a/tests/framework/session/test_session.py b/tests/framework/session/test_session.py
index 0e71181823..31b2ef5109 100644
--- a/tests/framework/session/test_session.py
+++ b/tests/framework/session/test_session.py
@@ -496,6 +496,28 @@ def test_git_describe_error(
         ]
         assert actual_log_messages == expected_log_messages
 
+    def test_get_username_error(self, fake_project, mock_package_name, mocker, caplog):
+        """Test that username information is not added to the session store
+        if call to getuser() fails
+        """
+        caplog.set_level(logging.DEBUG, logger="kedro")
+
+        mocker.patch("subprocess.check_output")
+        mocker.patch("kedro.framework.session.KedroSession._setup_logging")
+        mocker.patch("getpass.getuser", side_effect=FakeException("getuser error"))
+        session = KedroSession.create(mock_package_name, fake_project)
+        assert "username" not in session.store
+
+        expected_log_messages = [
+            "Unable to get username. Full exception: getuser error"
+        ]
+        actual_log_messages = [
+            rec.getMessage()
+            for rec in caplog.records
+            if rec.name == SESSION_LOGGER_NAME and rec.levelno == logging.DEBUG
+        ]
+        assert expected_log_messages == actual_log_messages
+
     @pytest.mark.usefixtures("mock_settings")
     def test_log_error(self, fake_project, mock_package_name):
         """Test logging the error by the session"""


Code review comments:
diff_hunk: @@ -496,6 +496,28 @@ def test_git_describe_error(
         ]
         assert actual_log_messages == expected_log_messages
 
+    def test_get_username_error(self, fake_project, mock_package_name, mocker, caplog):
+        """Test that username information is not added to the session store
+        if call to getuser() fails
+        """
+        caplog.set_level(logging.DEBUG, logger="kedro")
+
+        mocker.patch("subprocess.check_output")
+        mocker.patch("kedro.framework.session.KedroSession._setup_logging")
+        mocker.patch("getpass.getuser", side_effect=FakeException("getuser error"))
+        session = KedroSession.create(mock_package_name, fake_project)
+        assert "username" not in session.store
+
+        expected_log_messages = [
+            "Unable to get username. Full exception: getuser error"
+        ]
+        actual_log_messages = [
+            rec.getMessage()
+            for rec in caplog.records
+            if rec.name == SESSION_LOGGER_NAME and rec.levelno == logging.DEBUG
+        ]
+        assert expected_log_messages == actual_log_messages
path: tests/framework/session/test_session.py
text: @user1:
```suggestion
        assert actual_log_messages == expected_log_messages
```