mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-24 21:14:00 -07:00 
			
		
		
		
	[PR #9743/94e15110 backport][stable-10] incus_connection: Allow non-root users to connect to an instance (#9765)
incus_connection: Allow non-root users to connect to an instance (#9743)
* feat: add remote_user option to incus connection
* feat: add changelog fragment
* fix: formatting
(cherry picked from commit 94e1511005)
Co-authored-by: Peter Siegel <33677897+yeetypete@users.noreply.github.com>
	
	
This commit is contained in:
		
					parent
					
						
							
								d811807e1f
							
						
					
				
			
			
				commit
				
					
						35d6ab10bb
					
				
			
		
					 2 changed files with 102 additions and 15 deletions
				
			
		|  | @ -0,0 +1,2 @@ | |||
| minor_changes: | ||||
|   - incus connection plugin - adds ``remote_user`` and ``incus_become_method`` parameters for allowing a non-root user to connect to an Incus instance (https://github.com/ansible-collections/community.general/pull/9743). | ||||
|  | @ -32,6 +32,15 @@ options: | |||
|     vars: | ||||
|       - name: ansible_executable | ||||
|       - name: ansible_incus_executable | ||||
|   incus_become_method: | ||||
|     description: | ||||
|       - Become command used to switch to a non-root user. | ||||
|       - Is only used when O(remote_user) is not V(root). | ||||
|     type: str | ||||
|     default: /bin/su | ||||
|     vars: | ||||
|       - name: incus_become_method | ||||
|     version_added: 10.4.0 | ||||
|   remote: | ||||
|     description: | ||||
|       - The name of the Incus remote to use (per C(incus remote list)). | ||||
|  | @ -40,6 +49,22 @@ options: | |||
|     default: local | ||||
|     vars: | ||||
|       - name: ansible_incus_remote | ||||
|   remote_user: | ||||
|     description: | ||||
|       - User to login/authenticate as. | ||||
|       - Can be set from the CLI via the C(--user) or C(-u) options. | ||||
|     type: string | ||||
|     default: root | ||||
|     vars: | ||||
|       - name: ansible_user | ||||
|     env: | ||||
|       - name: ANSIBLE_REMOTE_USER | ||||
|     ini: | ||||
|       - section: defaults | ||||
|         key: remote_user | ||||
|     keyword: | ||||
|       - name: remote_user | ||||
|     version_added: 10.4.0 | ||||
|   project: | ||||
|     description: | ||||
|       - The name of the Incus project to use (per C(incus project list)). | ||||
|  | @ -64,7 +89,6 @@ class Connection(ConnectionBase): | |||
| 
 | ||||
|     transport = "incus" | ||||
|     has_pipelining = True | ||||
|     default_user = 'root' | ||||
| 
 | ||||
|     def __init__(self, play_context, new_stdin, *args, **kwargs): | ||||
|         super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) | ||||
|  | @ -79,10 +103,34 @@ class Connection(ConnectionBase): | |||
|         super(Connection, self)._connect() | ||||
| 
 | ||||
|         if not self._connected: | ||||
|             self._display.vvv("ESTABLISH Incus CONNECTION FOR USER: root", | ||||
|             self._display.vvv(f"ESTABLISH Incus CONNECTION FOR USER: {self.get_option('remote_user')}", | ||||
|                               host=self._instance()) | ||||
|             self._connected = True | ||||
| 
 | ||||
|     def _build_command(self, cmd) -> str: | ||||
|         """build the command to execute on the incus host""" | ||||
| 
 | ||||
|         exec_cmd = [ | ||||
|             self._incus_cmd, | ||||
|             "--project", self.get_option("project"), | ||||
|             "exec", | ||||
|             f"{self.get_option('remote')}:{self._instance()}", | ||||
|             "--"] | ||||
| 
 | ||||
|         if self.get_option("remote_user") != "root": | ||||
|             self._display.vvv( | ||||
|                 f"INFO: Running as non-root user: {self.get_option('remote_user')}, \ | ||||
|                 trying to run 'incus exec' with become method: {self.get_option('incus_become_method')}", | ||||
|                 host=self._instance(), | ||||
|             ) | ||||
|             exec_cmd.extend( | ||||
|                 [self.get_option("incus_become_method"), self.get_option("remote_user"), "-c"] | ||||
|             ) | ||||
| 
 | ||||
|         exec_cmd.extend([self.get_option("executable"), "-c", cmd]) | ||||
| 
 | ||||
|         return exec_cmd | ||||
| 
 | ||||
|     def _instance(self): | ||||
|         # Return only the leading part of the FQDN as the instance name | ||||
|         # as Incus instance names cannot be a FQDN. | ||||
|  | @ -95,13 +143,8 @@ class Connection(ConnectionBase): | |||
|         self._display.vvv(f"EXEC {cmd}", | ||||
|                           host=self._instance()) | ||||
| 
 | ||||
|         local_cmd = [ | ||||
|             self._incus_cmd, | ||||
|             "--project", self.get_option("project"), | ||||
|             "exec", | ||||
|             f"{self.get_option('remote')}:{self._instance()}", | ||||
|             "--", | ||||
|             self._play_context.executable, "-c", cmd] | ||||
|         local_cmd = self._build_command(cmd) | ||||
|         self._display.vvvvv(f"EXEC {local_cmd}", host=self._instance()) | ||||
| 
 | ||||
|         local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] | ||||
|         in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru') | ||||
|  | @ -120,6 +163,25 @@ class Connection(ConnectionBase): | |||
| 
 | ||||
|         return process.returncode, stdout, stderr | ||||
| 
 | ||||
|     def _get_remote_uid_gid(self) -> tuple[int, int]: | ||||
|         """Get the user and group ID of 'remote_user' from the instance.""" | ||||
| 
 | ||||
|         rc, uid_out, err = self.exec_command("/bin/id -u") | ||||
|         if rc != 0: | ||||
|             raise AnsibleError( | ||||
|                 f"Failed to get remote uid for user {self.get_option('remote_user')}: {err}" | ||||
|             ) | ||||
|         uid = uid_out.strip() | ||||
| 
 | ||||
|         rc, gid_out, err = self.exec_command("/bin/id -g") | ||||
|         if rc != 0: | ||||
|             raise AnsibleError( | ||||
|                 f"Failed to get remote gid for user {self.get_option('remote_user')}: {err}" | ||||
|             ) | ||||
|         gid = gid_out.strip() | ||||
| 
 | ||||
|         return int(uid), int(gid) | ||||
| 
 | ||||
|     def put_file(self, in_path, out_path): | ||||
|         """ put a file from local to Incus """ | ||||
|         super(Connection, self).put_file(in_path, out_path) | ||||
|  | @ -130,12 +192,35 @@ class Connection(ConnectionBase): | |||
|         if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')): | ||||
|             raise AnsibleFileNotFound(f"input path is not a file: {in_path}") | ||||
| 
 | ||||
|         if self.get_option("remote_user") != "root": | ||||
|             uid, gid = self._get_remote_uid_gid() | ||||
|             local_cmd = [ | ||||
|                 self._incus_cmd, | ||||
|             "--project", self.get_option("project"), | ||||
|             "file", "push", "--quiet", | ||||
|                 "--project", | ||||
|                 self.get_option("project"), | ||||
|                 "file", | ||||
|                 "push", | ||||
|                 "--uid", | ||||
|                 str(uid), | ||||
|                 "--gid", | ||||
|                 str(gid), | ||||
|                 "--quiet", | ||||
|                 in_path, | ||||
|             f"{self.get_option('remote')}:{self._instance()}/{out_path}"] | ||||
|                 f"{self.get_option('remote')}:{self._instance()}/{out_path}", | ||||
|             ] | ||||
|         else: | ||||
|             local_cmd = [ | ||||
|                 self._incus_cmd, | ||||
|                 "--project", | ||||
|                 self.get_option("project"), | ||||
|                 "file", | ||||
|                 "push", | ||||
|                 "--quiet", | ||||
|                 in_path, | ||||
|                 f"{self.get_option('remote')}:{self._instance()}/{out_path}", | ||||
|             ] | ||||
| 
 | ||||
|         self._display.vvvvv(f"PUT {local_cmd}", host=self._instance()) | ||||
| 
 | ||||
|         local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue