Repository: ansible-collections/community.general

Issue:
Add ForwardAgent to community.general.ssh_config
### Summary

I want to configure the ssh config on multiple servers using ansible.

Some of those servers should have `ForwardAgent yes`.

A setting for configuring it using Ansible would be very useful.

### Issue Type

Feature Idea

### Component Name

ssh_config

### Additional Information

```yaml (paste below)
- name: configure .ssh/config for myserver
  community.general.ssh_config:
    user: "{{ ansible_user_id }}"
    host: "myserver"
    hostname: "127.0.0.1"
    identity_file: "~/.ssh/private_key"
    state: present
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/system/ssh_config.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/system/ssh_config.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @Akasurde @gaqzi
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@MatthieuBizien Thanks for reporting this feature request. Let me check the feasibility and get back to you.


Hey @Akasurde 
Did you had time to look into this ?

Best.
@bmillemathias I am currently swamped up with other responsibilities. Feel free to engage and create feature.

Resolving patch:
diff --git a/plugins/modules/system/ssh_config.py b/plugins/modules/system/ssh_config.py
--- a/plugins/modules/system/ssh_config.py
+++ b/plugins/modules/system/ssh_config.py
@@ -76,6 +76,11 @@
     description:
       - Sets the C(ProxyCommand) option.
     type: str
+  forward_agent:
+    description:
+      - Sets the C(ForwardAgent) option.
+    type: bool
+    version_added: 4.0.0
   ssh_config_file:
     description:
       - SSH config file.
@@ -203,6 +208,12 @@ def ensure_state(self):
             proxycommand=self.params.get('proxycommand'),
         )
 
+        # Convert True / False to 'yes' / 'no' for usage in ssh_config
+        if self.params['forward_agent'] is True:
+            args['forward_agent'] = 'yes'
+        if self.params['forward_agent'] is False:
+            args['forward_agent'] = 'no'
+
         config_changed = False
         hosts_changed = []
         hosts_change_diff = []
@@ -288,6 +299,7 @@ def main():
             identity_file=dict(type='path'),
             port=dict(type='str'),
             proxycommand=dict(type='str', default=None),
+            forward_agent=dict(type='bool'),
             remote_user=dict(type='str'),
             ssh_config_file=dict(default=None, type='path'),
             state=dict(type='str', default='present', choices=['present', 'absent']),


Test patch:
diff --git a/tests/integration/targets/ssh_config/tasks/main.yml b/tests/integration/targets/ssh_config/tasks/main.yml
--- a/tests/integration/targets/ssh_config/tasks/main.yml
+++ b/tests/integration/targets/ssh_config/tasks/main.yml
@@ -219,3 +219,6 @@
       - short_name.hosts_added == ["full"]
       - short_name.hosts_changed == []
       - short_name.hosts_removed == []
+
+- name: Include integration tests for additional options (e.g. proxycommand)
+  include: 'options.yml'
diff --git a/tests/integration/targets/ssh_config/tasks/options.yml b/tests/integration/targets/ssh_config/tasks/options.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/ssh_config/tasks/options.yml
@@ -0,0 +1,194 @@
+# Reset ssh_config before testing options
+- name: Copy sample config file
+  copy:
+    src: 'files/ssh_config_test'
+    dest: '{{ ssh_config_test }}'
+
+- name: Options - Add in check mode
+  community.general.ssh_config:
+    ssh_config_file: "{{ ssh_config_test }}"
+    host: "options.example.com"
+    proxycommand: "ssh jumphost.example.com -W %h:%p"
+    forward_agent: true
+    state: present
+  register: options_add
+  check_mode: yes
+
+- name: Options - Check if changes are made in check mode
+  assert:
+    that:
+      - options_add.changed
+      - "'options.example.com' in options_add.hosts_added"
+      - options_add.hosts_changed is defined
+      - options_add.hosts_removed is defined
+
+- name: "Options - Get content of {{ ssh_config_test }}"
+  slurp:
+    src: "{{ ssh_config_test }}"
+  register: slurp_ssh_config
+
+- name: "Options - Verify that nothign was added to {{ ssh_config_test }} during change mode"
+  assert:
+    that:
+      - "'options.example.com' not in slurp_ssh_config['content'] | b64decode"
+
+- name: Options - Add a host
+  community.general.ssh_config:
+    ssh_config_file: "{{ ssh_config_test }}"
+    host: "options.example.com"
+    proxycommand: "ssh jumphost.example.com -W %h:%p"
+    forward_agent: true
+    state: present
+  register: options_add
+
+- name: Options - Check if changes are made
+  assert:
+    that:
+      - options_add.changed
+      - "'options.example.com' in options_add.hosts_added"
+      - options_add.hosts_changed is defined
+      - options_add.hosts_removed is defined
+
+- name: Options - Add same host again for idempotency
+  community.general.ssh_config:
+    ssh_config_file: "{{ ssh_config_test }}"
+    host: "options.example.com"
+    proxycommand: "ssh jumphost.example.com -W %h:%p"
+    forward_agent: true
+    state: present
+  register: options_add_again
+
+- name: Options - Check for idempotency
+  assert:
+    that:
+      - not options_add_again.changed
+      - options_add.hosts_changed is defined
+      - options_add.hosts_removed is defined
+      - options_add.hosts_added is defined
+
+- name: "Options - Get content of {{ ssh_config_test }}"
+  slurp:
+    src: "{{ ssh_config_test }}"
+  register: slurp_ssh_config
+
+- name: "Verify that {{ ssh_config_test }} contains added options"
+  assert:
+    that:
+      - "'proxycommand ssh jumphost.example.com -W %h:%p' in slurp_ssh_config['content'] | b64decode"
+      - "'forwardagent yes' in slurp_ssh_config['content'] | b64decode"
+
+- name: Options - Update host
+  community.general.ssh_config:
+    ssh_config_file: "{{ ssh_config_test }}"
+    host: "options.example.com"
+    proxycommand: "ssh new-jumphost.example.com -W %h:%p"
+    forward_agent: no
+    state: present
+  register: options_update
+
+- name: Options - Check for update operation
+  assert:
+    that:
+      - options_update.changed
+      - options_update.hosts_changed is defined
+      - "'options.example.com' in options_update.hosts_changed"
+      - options_update.hosts_removed is defined
+      - options_update.hosts_added is defined
+      - options_update.hosts_change_diff is defined
+
+- name: Options - Update host again
+  community.general.ssh_config:
+    ssh_config_file: "{{ ssh_config_test }}"
+    host: "options.example.com"
+    proxycommand: "ssh new-jumphost.example.com -W %h:%p"
+    forward_agent: no
+    state: present
+  register: options_update
+
+- name: Options - Check update operation for idempotency
+  assert:
+    that:
+      - not options_update.changed
+      - options_update.hosts_changed is defined
+      - options_update.hosts_removed is defined
+      - options_update.hosts_added is defined
+      - options_update.hosts_change_diff is defined
+
+- name: "Options - Get content of {{ ssh_config_test }}"
+  slurp:
+    src: "{{ ssh_config_test }}"
+  register: slurp_ssh_config
+
+- name: "Verify that {{ ssh_config_test }} contains changed options"
+  assert:
+    that:
+      - "'proxycommand ssh new-jumphost.example.com -W %h:%p' in slurp_ssh_config['content'] | b64decode"
+      - "'forwardagent no' in slurp_ssh_config['content'] | b64decode"
+
+- name: Options - Ensure no update in case option exist in ssh_config file but wasn't defined in playbook
+  community.general.ssh_config:
+    ssh_config_file: "{{ ssh_config_test }}"
+    host: "options.example.com"
+    state: present
+  register: options_no_update
+
+- name: Options - Check that no update took place
+  assert:
+    that:
+      - not options_update.changed
+      - options_update.hosts_changed is defined
+      - options_update.hosts_removed is defined
+      - options_update.hosts_added is defined
+      - options_update.hosts_change_diff is defined
+
+- name: "Options - Get content of {{ ssh_config_test }}"
+  slurp:
+    src: "{{ ssh_config_test }}"
+  register: slurp_ssh_config
+
+- name: "Verify that {{ ssh_config_test }} wasn't changed"
+  assert:
+    that:
+      - "'proxycommand ssh new-jumphost.example.com -W %h:%p' in slurp_ssh_config['content'] | b64decode"
+      - "'forwardagent no' in slurp_ssh_config['content'] | b64decode"
+
+- name: Options - Delete a host
+  community.general.ssh_config:
+    ssh_config_file: "{{ ssh_config_test }}"
+    host: "options.example.com"
+    state: absent
+  register: options_delete
+
+- name: Options - Check if host was removed
+  assert:
+    that:
+      - options_delete.changed
+      - "'options.example.com' in options_delete.hosts_removed"
+      - options_delete.hosts_changed is defined
+      - options_delete.hosts_added is defined
+
+- name: Options - Delete same host again for idempotency
+  community.general.ssh_config:
+    ssh_config_file: "{{ ssh_config_test }}"
+    host: "options.example.com"
+    state: absent
+  register: options_delete_again
+
+- name: Options - Check delete operation for idempotency
+  assert:
+    that:
+      - not options_delete_again.changed
+      - options_delete_again.hosts_changed is defined
+      - options_delete_again.hosts_removed is defined
+      - options_delete_again.hosts_added is defined
+
+- name: "Options - Get content of {{ ssh_config_test }}"
+  slurp:
+    src: "{{ ssh_config_test }}"
+  register: slurp_ssh_config
+
+- name: "Verify that {{ ssh_config_test }} does not contains deleted options"
+  assert:
+    that:
+      - "'proxycommand ssh new-jumphost.example.com -W %h:%p' not in slurp_ssh_config['content'] | b64decode"
+      - "'forwardagent no' not in slurp_ssh_config['content'] | b64decode"