From 77f54f067a8f23361ae4e061876ad6705965604b Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Thu, 4 May 2023 14:11:01 -0700 Subject: [PATCH] Allow to set user value for the container image (#521) * Allow to set user value for the container image * Added options.user setting * Updated documentation with an example * Added unit tests Fixes: #506 Signed-off-by: Abhijeet Kasurde * Updated indentation Signed-off-by: Abhijeet Kasurde * review comments Signed-off-by: Abhijeet Kasurde * Fix integration tests Signed-off-by: Abhijeet Kasurde --------- Signed-off-by: Abhijeet Kasurde --- demo/execution-environment.yml | 3 +++ docs/definition.rst | 5 +++++ src/ansible_builder/containerfile.py | 5 +++++ src/ansible_builder/ee_schema.py | 5 +++++ test/data/v3/complete/ee.yml | 3 +++ test/integration/test_create.py | 21 +++++++++++++++++++++ test/unit/test_user_definition.py | 26 ++++++++++++++++++++++++++ 7 files changed, 68 insertions(+) diff --git a/demo/execution-environment.yml b/demo/execution-environment.yml index e94ea8dc..3b8865fa 100644 --- a/demo/execution-environment.yml +++ b/demo/execution-environment.yml @@ -8,6 +8,9 @@ images: # name: registry.fedoraproject.org/fedora:38 # vanilla image! # name: registry.access.redhat.com/ubi9/ubi:latest # vanilla image! +options: + user: '1000' # (optional) sets the username or UID + dependencies: python_interpreter: package_system: python39 # (optional) name of a Python interpreter OS package to install diff --git a/docs/definition.rst b/docs/definition.rst index a7be855b..e0a80c3f 100644 --- a/docs/definition.rst +++ b/docs/definition.rst @@ -330,6 +330,10 @@ builder runtime functionality. Valid keys for this section are: created (if it doesn't already exist), set to ``root`` group ownership, and ``rwx`` group permissions recursively applied to it. The default value is ``/runner``. + ``user`` + This sets the username or UID to use as the default user for the final container image. + The default value ``1000``. + Example ``options`` section: @@ -344,6 +348,7 @@ Example ``options`` section: relax_password_permissions: false skip_ansible_check: true workdir: /myworkdir + user: bob version ******* diff --git a/src/ansible_builder/containerfile.py b/src/ansible_builder/containerfile.py index b030fa3e..9c57f908 100644 --- a/src/ansible_builder/containerfile.py +++ b/src/ansible_builder/containerfile.py @@ -170,6 +170,8 @@ def prepare(self): self._insert_custom_steps('append_final') self._prepare_label_steps() + if self.definition.version >= 3 and (uid := self.definition.options['user']): + self._prepare_user_steps(uid) self._prepare_entrypoint_steps() def write(self): @@ -424,3 +426,6 @@ def _prepare_entrypoint_steps(self): self.steps.append(f"ENTRYPOINT {ep}") if cmd := self.definition.container_init.get('cmd'): self.steps.append(f"CMD {cmd}") + + def _prepare_user_steps(self, uid): + self.steps.append(f"USER {uid}") diff --git a/src/ansible_builder/ee_schema.py b/src/ansible_builder/ee_schema.py index 82a58bb8..bc642239 100644 --- a/src/ansible_builder/ee_schema.py +++ b/src/ansible_builder/ee_schema.py @@ -345,6 +345,10 @@ "description": "Path to the system package manager to use", "type": "string", }, + "user": { + "description": "Sets the username or UID", + "type": "string", + }, "container_init": { "description": "Customize container startup behavior", "type": "object", @@ -433,3 +437,4 @@ def _handle_options_defaults(ee_def: dict): 'entrypoint': '["/output/scripts/entrypoint", "dumb-init"]', 'cmd': '["bash"]', }) + options.setdefault('user', '1000') diff --git a/test/data/v3/complete/ee.yml b/test/data/v3/complete/ee.yml index cf1e4377..a0bf3ee6 100644 --- a/test/data/v3/complete/ee.yml +++ b/test/data/v3/complete/ee.yml @@ -9,6 +9,9 @@ build_arg_defaults: ANSIBLE_GALAXY_CLI_COLLECTION_OPTS: '--foo' ANSIBLE_GALAXY_CLI_ROLE_OPTS: '--bar' +options: + user: '1001' + dependencies: ansible_core: package_pip: ansible-core==2.13 diff --git a/test/integration/test_create.py b/test/integration/test_create.py index a844ac8b..40966e6c 100644 --- a/test/integration/test_create.py +++ b/test/integration/test_create.py @@ -322,6 +322,7 @@ def test_v3_complete(cli, data_dir, tmp_path): assert 'RUN chmod ug+rw /etc/passwd' in text assert 'RUN mkdir -p /runner' in text assert 'ENTRYPOINT ["/output/scripts/entrypoint", "dumb-init"]' in text + assert 'USER 1001' in text # check additional_build_files myconfigs_path = tmp_path / constants.user_content_subfolder / "myconfigs" @@ -468,3 +469,23 @@ def test_v3_no_workdir(cli, build_dir_and_ee_yml): assert "WORKDIR" not in text.replace('WORKDIR /build', '') # intermediate stages set WORKDIR- ignore those assert "mkdir -p /runner" not in text + + +def test_v3_set_user_id(cli, build_dir_and_ee_yml): + """ + Test that a custom 'options.user' sets it + """ + tmpdir, eeyml = build_dir_and_ee_yml( + """ + version: 3 + options: + user: bob + """ + ) + cli(f'ansible-builder create -c {tmpdir} -f {eeyml} --output-filename Containerfile') + + containerfile = tmpdir / "Containerfile" + assert containerfile.exists() + text = containerfile.read_text() + + assert "USER bob" in text diff --git a/test/unit/test_user_definition.py b/test/unit/test_user_definition.py index a2cb4be3..b2011d56 100644 --- a/test/unit/test_user_definition.py +++ b/test/unit/test_user_definition.py @@ -215,6 +215,32 @@ def test_v3_skip_ansible_check_default(self, exec_env_definition_file): value = definition.raw.get('options', {}).get('skip_ansible_check') assert value is False + def test_v3_user_id(self, exec_env_definition_file): + """ + Test that options.user defaults to 1000 + """ + path = exec_env_definition_file( + "{'version': 3}" + ) + definition = UserDefinition(path) + definition.validate() + + value = definition.raw.get('options', {}).get('user') + assert value == '1000' + + def test_v3_set_user_name(self, exec_env_definition_file): + """ + Test that options.user sets to username + """ + path = exec_env_definition_file( + "{'version': 3, 'options': {'user': 'bob'}}" + ) + definition = UserDefinition(path) + definition.validate() + + value = definition.raw.get('options', {}).get('user') + assert value == 'bob' + class TestImageDescription: