Repository: ansible-collections/community.aws

Issue:
secretsmanager_secret module should not overwrite an existing Secret
### Summary

The [`community.aws.secretsmanager_secret`](https://docs.ansible.com/ansible/latest/collections/community/aws/secretsmanager_secret_module.html) module currently offers no option to *not* overwrite a Secret if it exists with the same name but a different value. This forces the user to first try to check if the Secret exists and then skip the task if it does. It will simply call `secrets_mgr.update_secret(secret)` and overwrite the existing one. If the intended Secret value itself is, for example, a random password, the option to only lookup that the Secret exists (but not that its values match) would be a nice feature.

Relevant code: https://github.com/ansible-collections/community.aws/blob/99978ef51ce1372d2f36b501b084b2bf54381073/plugins/modules/secretsmanager_secret.py#L479

### Issue Type

Feature Idea

### Component Name

secretsmanager_secret

### Additional Information

```yaml
- name: Try to retrive existing elastic secrets from AWS Secrets Manager
  ansible.builtin.set_fact:
    elastic_user_password: "{{ lookup('amazon.aws.aws_secret', clustername + '/' + elastic_namespace + '.elastic-user-password', nested=true, region=region, on_missing='error') }}"
    kibana_client_secret: "{{ lookup('amazon.aws.aws_secret', clustername + '/' + elastic_namespace + '.keycloak-secret', nested=true, region=region, on_missing='error') }}"
  register: secrets_found
  ignore_errors: true

- name: Create elastic user password and Keycloak AWS secrets if necessary
  community.aws.secretsmanager_secret:
    name: "{{ clustername }}/{{ elastic_namespace }}"
    description: Elastic secrets for {{ elastic_namespace }}
    state: present
    secret_type: "string"
    json_secret: {
      "elastic-user-password": "{{ lookup('community.general.random_string', length=16, special=false) }}",
      "keycloak-secret": "{{ lookup('community.general.random_string', length=16, special=false) }}"
    }
    region: "{{ region }}"
  when: secrets_found is failed
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct
secretsmanager_secret module should not overwrite an existing Secret
### Summary

The [`community.aws.secretsmanager_secret`](https://docs.ansible.com/ansible/latest/collections/community/aws/secretsmanager_secret_module.html) module currently offers no option to *not* overwrite a Secret if it exists with the same name but a different value. This forces the user to first try to check if the Secret exists and then skip the task if it does. It will simply call `secrets_mgr.update_secret(secret)` and overwrite the existing one. If the intended Secret value itself is, for example, a random password, the option to only lookup that the Secret exists (but not that its values match) would be a nice feature.

Relevant code: https://github.com/ansible-collections/community.aws/blob/99978ef51ce1372d2f36b501b084b2bf54381073/plugins/modules/secretsmanager_secret.py#L479

### Issue Type

Feature Idea

### Component Name

secretsmanager_secret

### Additional Information

```yaml
- name: Try to retrive existing elastic secrets from AWS Secrets Manager
  ansible.builtin.set_fact:
    elastic_user_password: "{{ lookup('amazon.aws.aws_secret', clustername + '/' + elastic_namespace + '.elastic-user-password', nested=true, region=region, on_missing='error') }}"
    kibana_client_secret: "{{ lookup('amazon.aws.aws_secret', clustername + '/' + elastic_namespace + '.keycloak-secret', nested=true, region=region, on_missing='error') }}"
  register: secrets_found
  ignore_errors: true

- name: Create elastic user password and Keycloak AWS secrets if necessary
  community.aws.secretsmanager_secret:
    name: "{{ clustername }}/{{ elastic_namespace }}"
    description: Elastic secrets for {{ elastic_namespace }}
    state: present
    secret_type: "string"
    json_secret: {
      "elastic-user-password": "{{ lookup('community.general.random_string', length=16, special=false) }}",
      "keycloak-secret": "{{ lookup('community.general.random_string', length=16, special=false) }}"
    }
    region: "{{ region }}"
  when: secrets_found is failed
```


### Code of Conduct

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


Discussion:
Files identified in the description:
* [`lib/ansible/plugins/lookup`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/lib/ansible/plugins/lookup)
* [`plugins/modules/secretsmanager_secret.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/plugins/modules/secretsmanager_secret.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 @jillr @markuman @rrey @s-hertel @tremble
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Files identified in the description:
* [`lib/ansible/plugins/lookup`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/lib/ansible/plugins/lookup)
* [`plugins/modules/secretsmanager_secret.py`](https://github.com/['ansible-collections/amazon.aws', 'ansible-collections/community.aws', 'ansible-collections/community.vmware']/blob/main/plugins/modules/secretsmanager_secret.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 @jillr @markuman @rrey @s-hertel @tremble
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/modules/secretsmanager_secret.py b/plugins/modules/secretsmanager_secret.py
--- a/plugins/modules/secretsmanager_secret.py
+++ b/plugins/modules/secretsmanager_secret.py
@@ -29,6 +29,14 @@
     default: 'present'
     choices: ['present', 'absent']
     type: str
+  overwrite:
+    description:
+    - Whether to overwrite an existing secret with the same name.
+    - If set to C(True), an existing secret with the same I(name) will be overwritten.
+    - If set to C(False), a secret with the given I(name) will only be created if none exists.
+    type: bool
+    default: True
+    version_added: 5.3.0
   recovery_window:
     description:
     - Only used if state is absent.
@@ -130,6 +138,14 @@
     state: absent
     secret_type: 'string'
     secret: "{{ super_secret_string }}"
+
+- name: Only create a new secret, but do not update if alredy exists by name
+  community.aws.secretsmanager_secret:
+    name: 'random_string'
+    state: present
+    secret_type: 'string'
+    secret: "{{ lookup('community.general.random_string', length=16, special=false) }}"
+    overwrite: false
 '''
 
 RETURN = r'''
@@ -524,6 +540,7 @@ def main():
         argument_spec={
             'name': dict(required=True),
             'state': dict(choices=['present', 'absent'], default='present'),
+            'overwrite': dict(type='bool', default=True),
             'description': dict(default=""),
             'replica': dict(type='list', elements='dict', options=replica_args),
             'kms_key_id': dict(),
@@ -580,12 +597,15 @@ def main():
                 result = secrets_mgr.put_resource_policy(secret)
             changed = True
         else:
+            # current_secret exists; decide what to do with it
             if current_secret.get("DeletedDate"):
                 secrets_mgr.restore_secret(secret.name)
                 changed = True
             if not secrets_mgr.secrets_match(secret, current_secret):
-                result = secrets_mgr.update_secret(secret)
-                changed = True
+                overwrite = module.params.get('overwrite')
+                if overwrite:
+                    result = secrets_mgr.update_secret(secret)
+                    changed = True
             if not rotation_match(secret, current_secret):
                 result = secrets_mgr.update_rotation(secret)
                 changed = True


Test patch:
diff --git a/tests/integration/targets/secretsmanager_secret/tasks/basic.yml b/tests/integration/targets/secretsmanager_secret/tasks/basic.yml
--- a/tests/integration/targets/secretsmanager_secret/tasks/basic.yml
+++ b/tests/integration/targets/secretsmanager_secret/tasks/basic.yml
@@ -688,6 +688,68 @@
       that:
         - result is not changed
 
+  # ============================================================
+  # Overwrite testing
+  # ============================================================
+
+  - name: Create secret with overwrite = False (Check mode)
+    aws_secret:
+      name: "{{ secret_name }}-2"
+      state: present
+      secret_type: 'string'
+      secret: "{{ super_secret_string }}"
+      overwrite: False
+    register: result
+    check_mode: True
+
+  - name: assert key is changed
+    assert:
+      that:
+        - result is changed
+
+  - name: Create secret with overwrite = False
+    aws_secret:
+      name: "{{ secret_name }}-2"
+      state: present
+      secret_type: 'string'
+      secret: "{{ super_secret_string }}"
+      overwrite: False
+    register: result
+
+  - name: assert key is changed
+    assert:
+      that:
+        - result is changed
+
+  - name: Update secret with overwrite = False (Check mode)
+    aws_secret:
+      name: "{{ secret_name }}-2"
+      state: present
+      secret_type: 'string'
+      secret: "{{ super_secret_string }}-2"
+      overwrite: False
+    register: result
+    check_mode: True
+
+  - name: assert key is not changed
+    assert:
+      that:
+        - result is not changed
+
+  - name: Create secret with overwrite = False
+    aws_secret:
+      name: "{{ secret_name }}-2"
+      state: present
+      secret_type: 'string'
+      secret: "{{ super_secret_string }}-2"
+      overwrite: False
+    register: result
+
+  - name: assert key is not changed
+    assert:
+      that:
+        - result is not changed
+
   # ============================================================
   # Removal testing
   # ============================================================
@@ -749,3 +811,10 @@
       state: absent
       recovery_window: 0
     ignore_errors: yes
+
+  - name: remove secret 2
+    aws_secret:
+      name: "{{ secret_name }}-2"
+      state: absent
+      recovery_window: 0
+    ignore_errors: yes