[Local Agent] Sync go/nest-cl/284433

Change-Id: I2df145027d29acd1383526ee5fcef08c09903d82
diff --git a/README.md b/README.md
index a320a82..cce81d0 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,8 @@
 
 ## Usage
 
+### Run Local Agent
+
 You should be running the Smart Home testsuite from the frontend web app to link and use the local agent.
 
 1. On the web app's Test Plan page, you should see a ```Local Agent Setup``` button. Click on it, and if you haven’t linked your local agent before, you should see a linking code. Copy the linking code.
@@ -170,7 +172,7 @@
  
 Other Devices                  Alias           Type                     Model                Available 
 ------------------------------ --------------- ------------------------ -------------------- ----------
- 
+
 1 total Gazoo device(s) available.
 20211207 14:41:15.339 I local_agent.py:258 Polling JSON-RPC requests from AMS.
 20211207 14:41:16.362 I local_agent.py:258 Polling JSON-RPC requests from AMS.
@@ -179,8 +181,21 @@
 20211207 14:41:20.456 I local_agent.py:249 Stopped reporting info because stop event is set.
 ```
 
+### Check and Update Local Agent version
 
-## Update Local Agent
+You won't be able to run Local Agent if it's outdated (the current version is
+below the minimal required version showed on the web app).
+Check the version of your Local Agent:
+
+```
+$ local-agent -v
+```
+
+or
+
+```
+$ local-agent -version
+```
 
 If your Local Agent is outdated, you'll need to update the package manually:
 
diff --git a/local_agent/local_agent.py b/local_agent/local_agent.py
index e05afb9..25398d1 100644
--- a/local_agent/local_agent.py
+++ b/local_agent/local_agent.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Google LLC
+# Copyright 2022 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@
 logger = logger_module.get_logger()
 
 # ========================= Constants / Configs ========================= #
+VERSION_FLAGS = {"-v", "-version", "--version"}
 APP_ENGINE_DATA_SIZE_LIMIT = 31 * 1048576  # 31 MB
 APP_ENGINE_DATA_SIZE_LIMIT_HUMAN_READABLE = '31 MB'
 DEFAULT_ARTIFACTS_DIR = '/tmp/local_agent_artifacts'
@@ -583,8 +584,19 @@
         gazoo_device.register(controller)
 
 
+def exit_if_query_module_versions() -> None:
+    """Exits the process if querying the Local Agent and GDM versions."""
+    if VERSION_FLAGS & set(sys.argv):
+        la_version = f'\n******* Local Agent version {version.__version__} *******'
+        gdm_version = f'\n******* GDM version {gazoo_device.__version__} *******'
+        logger.info(la_version + gdm_version)
+        sys.exit(0)
+
+
 def main() -> None:
     """Main entry of Local Agent."""
+    exit_if_query_module_versions()
+
     user_config, matter_controllers_config = read_config()
 
     ams_host = user_config.get(_USER_CONFIG_AMS_HOST, _EAP_AMS_HOST)
diff --git a/local_agent/tests/unit_tests/test_local_agent.py b/local_agent/tests/unit_tests/test_local_agent.py
index 3f52f66..184685b 100644
--- a/local_agent/tests/unit_tests/test_local_agent.py
+++ b/local_agent/tests/unit_tests/test_local_agent.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Google LLC
+# Copyright 2022 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
 import configparser
 import os
 import shutil
+import sys
 import tempfile
 import threading
 import unittest
@@ -78,7 +79,7 @@
     @mock.patch.object(local_agent.LocalAgentProcess,
                        '_read_auths',
                        return_value=('agent-id', 'agent-secret'))
-    def test_01_setup_credentials_existing_credential_success(
+    def test_setup_credentials_existing_credential_success(
         self,
         mock_read_auths,
         mock_set_local_agent_credentials):
@@ -87,7 +88,7 @@
 
     @mock.patch.object(builtins, 'input', side_effect=KeyboardInterrupt)
     @mock.patch.object(local_agent.LocalAgentProcess, '_read_auths')
-    def test_01_setup_credentials_no_existing_credential_will_start_linking(
+    def test_setup_credentials_no_existing_credential_will_start_linking(
         self, mock_read_auths, mock_input):
         """Verifies _setup_credentials starts linking if no credentials."""
         mock_read_auths.side_effect = FileNotFoundError
@@ -101,7 +102,7 @@
     @mock.patch.object(local_agent.LocalAgentProcess,
                        '_read_auths',
                        return_value=('agent-id', 'agent-secret'))
-    def test_01_setup_credentials_bad_existing_credential_will_start_linking(
+    def test_setup_credentials_bad_existing_credential_will_start_linking(
         self,
         mock_read_auths,
         mock_set_local_agent_credentials,
@@ -115,7 +116,7 @@
     @mock.patch.object(local_agent.LocalAgentProcess,
                        '_read_auths',
                        side_effect=FileNotFoundError)
-    def test_01_setup_credentials_start_linking_and_succeed(
+    def test_setup_credentials_start_linking_and_succeed(
         self, mock_read_auths, mock_inpnut, mock_register):
         """Verifies _setup_credentials starts linking and succeeds."""
         mock_register.return_value = ('the-agent-id', 'the-agent-secret')
@@ -127,7 +128,7 @@
     @mock.patch.object(local_agent.LocalAgentProcess,
                        '_read_auths',
                        side_effect=FileNotFoundError)
-    def test_01_setup_credentials_start_linking_and_keeps_retry(
+    def test_setup_credentials_start_linking_and_keeps_retry(
         self,
         mock_read_auths,
         mock_input,
@@ -145,19 +146,19 @@
         self.assertTrue(self.proc._setup_credentials())
         self.assertEqual(7, mock_input.call_count)
 
-    def test_03_read_write_auth(self):
+    def test_read_write_auth(self):
         """Verifies reading/writing auths."""
         self.proc._write_auths(_FAKE_AGENT_ID, _FAKE_AGENT_SECRET)
         self.assertEqual(
             (_FAKE_AGENT_ID, _FAKE_AGENT_SECRET), self.proc._read_auths())
 
-    def test_05_read_config_with_inexistent_file(self):
+    def test_read_config_with_inexistent_file(self):
         """Verifies read_config returns {} when config file doesn't exist."""
         local_agent.DEFAULT_USER_CONFIG = ''
         self.assertEqual(({}, {}), local_agent.read_config())
 
     @mock.patch.object(configparser, 'ConfigParser')
-    def test_05_read_config_missing_root_key(self, mock_parser):
+    def test_read_config_missing_root_key(self, mock_parser):
         """Verifies read_config raise ValueError when root key not present."""
         mock_config = mock.MagicMock()
         mock_parser.return_value = mock_config
@@ -166,7 +167,7 @@
             local_agent.read_config()
 
     @mock.patch.object(configparser, 'ConfigParser')
-    def test_05_read_config_success(self, mock_parser):
+    def test_read_config_success(self, mock_parser):
         """Verifies read_config run successfully."""
         mock_config = mock.MagicMock()
         mock_parser.return_value = mock_config
@@ -184,7 +185,7 @@
     @mock.patch.object(
         local_agent.LocalAgentProcess, '_setup_credentials', return_value=True)
     @mock.patch.object(threading.Event, 'is_set', side_effect=(False, True))
-    def test_06_run_starts_two_top_level_threads(
+    def test_run_starts_two_top_level_threads(
         self,
         mock_event_is_set,
         mock_setup_credentials,
@@ -203,7 +204,7 @@
     @mock.patch.object(local_agent.LocalAgentProcess,
                        '_setup_credentials',
                        return_value=False)
-    def test_06_run_will_exit_if_cannot_setup_credentials(
+    def test_run_will_exit_if_cannot_setup_credentials(
         self,
         mock_setup_credentials,
         mock_start_rpc_polling_thread,
@@ -221,7 +222,7 @@
     @mock.patch.object(suite_session_manager.SuiteSessionManager, 'start')
     @mock.patch.object(
         local_agent.LocalAgentProcess, '_setup_credentials', return_value=True)
-    def test_06_run_exits_main_thread_if_report_info_thread_is_dead(
+    def test_run_exits_main_thread_if_report_info_thread_is_dead(
         self,
         _,
         mock_start,
@@ -240,7 +241,7 @@
     @mock.patch.object(suite_session_manager.SuiteSessionManager, 'start')
     @mock.patch.object(
         local_agent.LocalAgentProcess, '_setup_credentials', return_value=True)
-    def test_06_run_exits_main_thread_if_poll_rpc_thread_is_dead(
+    def test_run_exits_main_thread_if_poll_rpc_thread_is_dead(
         self,
         _,
         mock_start,
@@ -254,10 +255,10 @@
     @mock.patch.object(translation_layer.TranslationLayer, 'detect_devices')
     @mock.patch.object(ams_client.AmsClient, 'report_info')
     @mock.patch.object(threading.Event, 'wait', return_value=True)
-    def test_07_report_info_sends_request_to_ams(self,
-                                                 mock_event_wait,
-                                                 mock_report_info,
-                                                 mock_detect_devices):
+    def test_report_info_sends_request_to_ams(self,
+                                              mock_event_wait,
+                                              mock_report_info,
+                                              mock_detect_devices):
         """Verifies _report_info uses AmsClient to report info."""
         mock_detect_devices.return_value = []
         self.proc._report_info()
@@ -267,10 +268,10 @@
     @mock.patch.object(translation_layer.TranslationLayer, 'detect_devices')
     @mock.patch.object(ams_client.AmsClient, 'report_info')
     @mock.patch.object(threading.Event, 'wait', return_value=True)
-    def test_07_report_info_wont_break_when_api_error(self,
-                                                      mock_event_wait,
-                                                      mock_report_info,
-                                                      mock_detect_devices):
+    def test_report_info_wont_break_when_api_error(self,
+                                                   mock_event_wait,
+                                                   mock_report_info,
+                                                   mock_detect_devices):
         """Verifies _report_info continues when an API error happens."""
         mock_report_info.side_effect = errors.ApiError
         self.proc._report_info()
@@ -278,10 +279,10 @@
     @mock.patch.object(translation_layer.TranslationLayer, 'detect_devices')
     @mock.patch.object(ams_client.AmsClient, 'report_info')
     @mock.patch.object(threading.Event, 'wait', return_value=True)
-    def test_07_report_info_wont_break_when_api_timeout(self,
-                                                        mock_event_wait,
-                                                        mock_report_info,
-                                                        mock_detect_devices):
+    def test_report_info_wont_break_when_api_timeout(self,
+                                                     mock_event_wait,
+                                                     mock_report_info,
+                                                     mock_detect_devices):
         """Verifies _report_info continues when an API error happens."""
         mock_report_info.side_effect = errors.ApiTimeoutError
         self.proc._report_info()
@@ -290,10 +291,10 @@
         local_agent.LocalAgentProcess, '_clean_up_and_terminate_agent')
     @mock.patch.object(translation_layer.TranslationLayer, 'detect_devices')
     @mock.patch.object(ams_client.AmsClient, 'report_info')
-    def test_07_report_info_break_when_agent_unlinked(self,
-                                                      mock_report_info,
-                                                      mock_detect_devices,
-                                                      mock_clean_up):
+    def test_report_info_break_when_agent_unlinked(self,
+                                                   mock_report_info,
+                                                   mock_detect_devices,
+                                                   mock_clean_up):
         """Verifies _report_info breaks when the local agent is unlinked."""
         mock_report_info.side_effect = errors.UnlinkedError
 
@@ -306,7 +307,7 @@
                        'get_rpc_request_from_ams',
                        return_value=None)
     @mock.patch.object(threading.Event, 'is_set')
-    def test_08_poll_rpc_gets_request_from_ams(
+    def test_poll_rpc_gets_request_from_ams(
         self, mock_event_is_set, mock_get_rpc_request):
         """Verifies _poll_rpc will get request from AMS in each iteration."""
         # We let there be 2 iterations.
@@ -317,7 +318,7 @@
     @mock.patch.object(ams_client.AmsClient, 'remove_rpc_request_from_ams')
     @mock.patch.object(ams_client.AmsClient, 'get_rpc_request_from_ams')
     @mock.patch.object(threading.Event, 'is_set', side_effect=(False, True))
-    def test_08_poll_rpc_continues_if_api_error_when_getting_request(
+    def test_poll_rpc_continues_if_api_error_when_getting_request(
         self,
         mock_event_is_set,
         mock_get_rpc_request,
@@ -330,7 +331,7 @@
     @mock.patch.object(ams_client.AmsClient, 'remove_rpc_request_from_ams')
     @mock.patch.object(ams_client.AmsClient, 'get_rpc_request_from_ams')
     @mock.patch.object(threading.Event, 'is_set', side_effect=(False, True))
-    def test_08_poll_rpc_continues_if_api_timeout_when_getting_request(
+    def test_poll_rpc_continues_if_api_timeout_when_getting_request(
         self,
         mock_event_is_set,
         mock_get_rpc_request,
@@ -344,7 +345,7 @@
         local_agent.LocalAgentProcess, '_clean_up_and_terminate_agent')
     @mock.patch.object(ams_client.AmsClient, 'get_rpc_request_from_ams')
     @mock.patch.object(threading.Event, 'is_set', return_value=False)
-    def test_08_poll_rpc_raises_if_agent_unlinked_when_getting_request(
+    def test_poll_rpc_raises_if_agent_unlinked_when_getting_request(
         self,
         mock_event_is_set,
         mock_get_rpc_request,
@@ -361,7 +362,7 @@
     @mock.patch.object(ams_client.AmsClient, 'remove_rpc_request_from_ams')
     @mock.patch.object(ams_client.AmsClient, 'get_rpc_request_from_ams')
     @mock.patch.object(threading.Event, 'is_set', side_effect=(False, True))
-    def test_08_poll_rpc_removes_rpc_request_from_ams_and_executes(
+    def test_poll_rpc_removes_rpc_request_from_ams_and_executes(
         self,
         mock_event_is_set,
         mock_get_rpc_request,
@@ -379,7 +380,7 @@
 
     @mock.patch.object(ams_client.AmsClient, 'remove_rpc_request_from_ams')
     @mock.patch.object(ams_client.AmsClient, 'get_rpc_request_from_ams')
-    def test_08_poll_rpc_terminate_local_agent_when_remove_rpc_fails(
+    def test_poll_rpc_terminate_local_agent_when_remove_rpc_fails(
         self,
         mock_get_request,
         mock_remove_request):
@@ -390,7 +391,7 @@
         self.proc._poll_rpc()
 
     @mock.patch.object(futures.ThreadPoolExecutor, 'submit')
-    def test_09_start_rpc_execution_thread_submits_to_thread_pool_executor(
+    def test_start_rpc_execution_thread_submits_to_thread_pool_executor(
         self, mock_submit):
         """Verifies _start_rpc_execution_thread submits to ThreadPoolExecutor.
         """
@@ -413,19 +414,19 @@
             self.proc._callback_for_rpc_execution_complete)
 
     @mock.patch.object(futures.ThreadPoolExecutor, 'shutdown')
-    def test_10_terminate_shutdown_pool_executor(self, mock_shutdown):
+    def test_terminate_shutdown_pool_executor(self, mock_shutdown):
         """Verifies _terminate shuts down the ThreadPoolExecutor."""
         self.proc._terminate(None, None)
         self.assertEqual(1, mock_shutdown.call_count)
 
     @mock.patch.object(threading.Event, 'set')
-    def test_10_terminate_sets_threading_event(self, mock_set):
+    def test_terminate_sets_threading_event(self, mock_set):
         """Verifies _terminate sets the threading event."""
         self.proc._terminate(None, None)
         mock_set.assert_called_once()
 
     @mock.patch.object(local_agent, 'logger')
-    def test_10_terminate_thread_still_alive(self, mock_logger):
+    def test_terminate_thread_still_alive(self, mock_logger):
         """Verifies _terminates on failure with still alive threads."""
         mock_rpc_thread = mock.Mock()
         mock_rpc_thread.is_alive.return_value = True
@@ -440,7 +441,7 @@
     @mock.patch.object(local_agent, 'os')
     @mock.patch.object(shutil, 'rmtree')
     @mock.patch.object(shutil, 'make_archive')
-    def test_11_compress_artifacts_and_upload_on_success(
+    def test_compress_artifacts_and_upload_on_success(
         self, mock_make, mock_rm, mock_os, mock_ams_upload):
         """Verifies _compress_artifacts_and_upload on success."""
         mock_os.stat.return_value.st_size = 1
@@ -455,7 +456,7 @@
     @mock.patch.object(os, 'stat')
     @mock.patch.object(shutil, 'rmtree')
     @mock.patch.object(shutil, 'make_archive')
-    def test_11_compress_artifacts_and_upload_too_large_file(
+    def test_compress_artifacts_and_upload_too_large_file(
         self, mock_make, mock_rm, mock_stat):
         """
         Verifies _compress_artifacts_and_upload on failure with too large file.
@@ -474,7 +475,7 @@
     @mock.patch.object(os, 'stat')
     @mock.patch.object(shutil, 'rmtree')
     @mock.patch.object(shutil, 'make_archive')
-    def test_11_compress_artifacts_and_upload_uploading_timed_out(
+    def test_compress_artifacts_and_upload_uploading_timed_out(
         self, mock_make, mock_rm, mock_stat, mock_ams_client_upload):
         """
         Verifies _compress_artifacts_and_upload on failure due to API timed out.
@@ -490,7 +491,7 @@
     @mock.patch.object(os, 'stat')
     @mock.patch.object(shutil, 'rmtree')
     @mock.patch.object(shutil, 'make_archive')
-    def test_11_compress_artifacts_and_upload_uploading_api_error(
+    def test_compress_artifacts_and_upload_uploading_api_error(
         self, mock_make, mock_rm, mock_stat, mock_ams_client_upload):
         """
         Verifies _compress_artifacts_and_upload on failure due to API error.
@@ -505,12 +506,14 @@
     @mock.patch.object(local_agent, 'LocalAgentProcess')
     @mock.patch.object(local_agent, 'register_extension_controllers')
     @mock.patch.object(local_agent, 'read_config')
-    def test_12_main_entry(self, mock_read, mock_register, mock_proc):
+    @mock.patch.object(local_agent, 'exit_if_query_module_versions')
+    def test_main_entry(self, mock_query, mock_read, mock_register, mock_proc):
         """Verifies local agent main entry on success."""
         mock_read.return_value = {}, []
 
         local_agent.main()
 
+        mock_query.assert_called_once()
         mock_read.assert_called_once()
         mock_register.assert_called_once()
         mock_proc.assert_called_once()
@@ -519,7 +522,7 @@
     @mock.patch.object(translation_layer.TranslationLayer,'is_rpc_timeout')
     @mock.patch.object(ams_client.AmsClient, 'send_rpc_response')
     @mock.patch.object(local_agent.LocalAgentProcess, '_handle_rpc_request')
-    def test_13_execute_rpc_executes_and_sends_result_to_ams(
+    def test_execute_rpc_executes_and_sends_result_to_ams(
         self,
         mock_handle_request,
         mock_send_rpc_response,
@@ -539,7 +542,7 @@
     @mock.patch.object(local_agent, 'logger')
     @mock.patch.object(ams_client.AmsClient, 'send_rpc_response')
     @mock.patch.object(local_agent.LocalAgentProcess, '_handle_rpc_request')
-    def test_13_execute_rpc_fail_to_send_rpc_response(
+    def test_execute_rpc_fail_to_send_rpc_response(
         self,
         mock_handle_request,
         mock_send_rpc_response,
@@ -560,7 +563,7 @@
     @mock.patch.object(translation_layer.TranslationLayer,'is_rpc_timeout')
     @mock.patch.object(local_agent, 'logger')
     @mock.patch.object(local_agent.LocalAgentProcess, '_handle_rpc_request')
-    def test_13_execute_rpc_not_sending_timeout_rpc_response(
+    def test_execute_rpc_not_sending_timeout_rpc_response(
         self,
         mock_handle_request,
         mock_logger,
@@ -575,7 +578,7 @@
 
     @mock.patch.object(
         suite_session_manager.SuiteSessionManager, 'start_test_suite')
-    def test_14_handle_rpc_request_start_suite(self, mock_start):
+    def test_handle_rpc_request_start_suite(self, mock_start):
         """Verifies handle_rpc_request to start suite on success."""
         mock_start.return_value = _FAKE_RPC_RESPONSE
         fake_rpc_request = {'method': _START_TEST_SUITE}
@@ -587,7 +590,7 @@
 
     @mock.patch.object(
         suite_session_manager.SuiteSessionManager, 'end_test_suite')
-    def test_14_handle_rpc_request_end_suite(self, mock_end):
+    def test_handle_rpc_request_end_suite(self, mock_end):
         """Verifies handle_rpc_request to end suite on success."""
         mock_end.return_value = _FAKE_RPC_RESPONSE
         fake_rpc_request = {'method': _END_TEST_SUITE}
@@ -599,7 +602,7 @@
 
     @mock.patch.object(
         translation_layer.TranslationLayer, 'dispatch_to_cmd_handler')
-    def test_14_handle_rpc_request_device_control(self, mock_dispatch):
+    def test_handle_rpc_request_device_control(self, mock_dispatch):
         """Verifies handle_rpc_request to control device on success."""
         mock_dispatch.return_value = _FAKE_RPC_RESPONSE
         fake_rpc_request = {'method': _LOCK_DEVICE}
@@ -610,7 +613,7 @@
         mock_dispatch.assert_called_once_with(fake_rpc_request)
 
     @mock.patch.object(local_agent, 'rpc_request_type', return_value='')
-    def test_14_handle_rpc_request_invalid_rpc_type(self, mock_req_type):
+    def test_handle_rpc_request_invalid_rpc_type(self, mock_req_type):
         """Verifies handle_rpc_request on failure with invalid RPC type."""
         error_msg = 'Invalid RPC request type'
 
@@ -620,7 +623,7 @@
 
     @mock.patch.object(
         translation_layer.TranslationLayer, 'dispatch_to_cmd_handler')
-    def test_14_handle_rpc_unexpected_errors(self, mock_dispatch):
+    def test_handle_rpc_unexpected_errors(self, mock_dispatch):
         """Verifies handle_rpc_request on failure with unexpected errors."""
         mock_dispatch.side_effect = RuntimeError(_FAKE_ERROR_MSG)
         fake_rpc_request = {'id': _FAKE_RPC_ID, 'method': _LOCK_DEVICE}
@@ -632,7 +635,7 @@
     @mock.patch.object(os, 'remove')
     @mock.patch.object(os.path, 'exists', return_value=True)
     @mock.patch.object(suite_session_manager.SuiteSessionManager, 'clean_up')
-    def test_15_clean_up_and_terminate_agent_on_success(
+    def test_clean_up_and_terminate_agent_on_success(
         self, mock_clean_up, mock_exists, mock_rm):
         """Verifies _clean_up_and_terminate_agent on success."""
         self.proc._clean_up_and_terminate_agent(remove_auth_file=True)
@@ -640,7 +643,7 @@
         mock_rm.assert_called_once()
 
     @mock.patch.object(local_agent, 'logger')
-    def test_16_callback_for_rpc_execution_complete(self, mock_logger):
+    def test_callback_for_rpc_execution_complete(self, mock_logger):
         """Verifies _callback_for_rpc_execution_complete on success."""
         mock_future = mock.Mock()
         self.proc._rpc_execution_future_ids.add(id(mock_future))
@@ -652,7 +655,7 @@
 
     @mock.patch.object(local_agent, 'gazoo_device')
     @mock.patch.object(local_agent, 'importlib')
-    def test_17_register_extension_controllers_on_success(
+    def test_register_extension_controllers_on_success(
         self, mock_importlib, mock_gazoo_device):
         """Verifies register_extension_controllers on success."""
         matter_controllers_config = [(_FAKE_CONTROLLER_PACKAGE, '')]
@@ -662,6 +665,13 @@
         mock_importlib.import_module.assert_called_once()
         mock_gazoo_device.register.assert_called_once()
 
+    @mock.patch.object(sys, 'exit')
+    def test_exit_if_query_module_versions(self, mock_exit):
+        """Verifies exit_if_query_module_versions on success."""
+        with mock.patch.object(sys, 'argv', ['-v']):
+            local_agent.exit_if_query_module_versions()
+            mock_exit.assert_called_once()
+
 
 if __name__ == '__main__':
     unittest.main(failfast=True)
diff --git a/local_agent/version.py b/local_agent/version.py
index a47123c..6daa0a5 100644
--- a/local_agent/version.py
+++ b/local_agent/version.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Google LLC
+# Copyright 2022 Google LLC
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,4 +18,4 @@
 version.
 """
 
-__version__ = '0.0.1'
+__version__ = '0.0.2'