diff --git a/lib/ansible/modules/source_control/git_config.py b/lib/ansible/modules/source_control/git_config.py index 620a885b60a..da8fd8697b0 100644 --- a/lib/ansible/modules/source_control/git_config.py +++ b/lib/ansible/modules/source_control/git_config.py @@ -51,6 +51,14 @@ options: also specify the repo parameter. It defaults to system only when not using I(list_all)=yes. choices: [ "local", "global", "system" ] + state: + description: + - "Indicates the setting should be set/unset. + This parameter has higher precedence than I(value) parameter: + when I(state)=absent and I(value) is defined, I(value) is discarded." + choices: [ 'present', 'absent' ] + default: 'present' + version_added: '2.8' value: description: - When specifying the name of a single setting, supply a value to @@ -69,6 +77,12 @@ EXAMPLES = ''' scope: global value: status +# Unset some settings in ~/.gitconfig +- git_config: + name: alias.ci + scope: global + state: absent + # Or system-wide: - git_config: name: alias.remotev @@ -149,9 +163,10 @@ def main(): name=dict(type='str'), repo=dict(type='path'), scope=dict(required=False, type='str', choices=['local', 'global', 'system']), + state=dict(required=False, type='str', default='present', choices=['present', 'absent']), value=dict(required=False) ), - mutually_exclusive=[['list_all', 'name'], ['list_all', 'value']], + mutually_exclusive=[['list_all', 'name'], ['list_all', 'value'], ['list_all', 'state']], required_if=[('scope', 'local', ['repo'])], required_one_of=[['list_all', 'name']], supports_check_mode=True, @@ -175,6 +190,12 @@ def main(): else: scope = 'system' + if params['state'] == 'absent': + unset = 'unset' + params['value'] = None + else: + unset = None + if params['value']: new_value = params['value'] else: @@ -212,16 +233,22 @@ def main(): k, v = value.split('=', 1) config_values[k] = v module.exit_json(changed=False, msg='', config_values=config_values) - elif not new_value: + elif not new_value and not unset: module.exit_json(changed=False, msg='', config_value=out.rstrip()) + elif unset and not out: + module.exit_json(changed=False, msg='no setting to unset') else: old_value = out.rstrip() if old_value == new_value: module.exit_json(changed=False, msg="") if not module.check_mode: - new_value_quoted = shlex_quote(new_value) - cmd = ' '.join(args + [new_value_quoted]) + if unset: + args.insert(len(args) - 1, "--" + unset) + cmd = ' '.join(args) + else: + new_value_quoted = shlex_quote(new_value) + cmd = ' '.join(args + [new_value_quoted]) (rc, out, err) = module.run_command(cmd, cwd=dir) if err: module.fail_json(rc=rc, msg=err, cmd=cmd) @@ -232,7 +259,7 @@ def main(): before_header=' '.join(args), before=old_value + "\n", after_header=' '.join(args), - after=new_value + "\n" + after=(new_value or '') + "\n" ), changed=True ) diff --git a/test/integration/targets/git_config/aliases b/test/integration/targets/git_config/aliases new file mode 100644 index 00000000000..dc0ba495eab --- /dev/null +++ b/test/integration/targets/git_config/aliases @@ -0,0 +1,2 @@ +shippable/posix/group3 + diff --git a/test/integration/targets/git_config/files/gitconfig b/test/integration/targets/git_config/files/gitconfig new file mode 100644 index 00000000000..989aa1c8bfa --- /dev/null +++ b/test/integration/targets/git_config/files/gitconfig @@ -0,0 +1,2 @@ +[http] + proxy = foo \ No newline at end of file diff --git a/test/integration/targets/git_config/tasks/exclusion_state_list-all.yml b/test/integration/targets/git_config/tasks/exclusion_state_list-all.yml new file mode 100644 index 00000000000..fe8b2fd239a --- /dev/null +++ b/test/integration/targets/git_config/tasks/exclusion_state_list-all.yml @@ -0,0 +1,16 @@ +--- +- import_tasks: setup_no_value.yml + +- name: testing exclusion between state and list_all parameters + git_config: + list_all: true + state: absent + register: result + ignore_errors: yes + +- name: assert git_config failed + assert: + that: + - result is failed + - "result.msg == 'parameters are mutually exclusive: list_all, state'" +... diff --git a/test/integration/targets/git_config/tasks/get_set_no_state.yml b/test/integration/targets/git_config/tasks/get_set_no_state.yml new file mode 100644 index 00000000000..149a9b2d93e --- /dev/null +++ b/test/integration/targets/git_config/tasks/get_set_no_state.yml @@ -0,0 +1,25 @@ +--- +- import_tasks: setup_no_value.yml + +- name: setting value without state + git_config: + name: "{{ option_name }}" + value: "{{ option_value }}" + scope: "{{ option_scope }}" + register: set_result + +- name: getting value without state + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + register: get_result + +- name: assert set changed and value is correct + assert: + that: + - set_result.changed == true + - set_result.diff.before == "\n" + - set_result.diff.after == option_value + "\n" + - get_result.changed == false + - get_result.config_value == option_value +... diff --git a/test/integration/targets/git_config/tasks/get_set_state_present.yml b/test/integration/targets/git_config/tasks/get_set_state_present.yml new file mode 100644 index 00000000000..59f3c9c0ee3 --- /dev/null +++ b/test/integration/targets/git_config/tasks/get_set_state_present.yml @@ -0,0 +1,27 @@ +--- +- import_tasks: setup_no_value.yml + +- name: setting value with state=present + git_config: + name: "{{ option_name }}" + value: "{{ option_value }}" + scope: "{{ option_scope }}" + state: present + register: result + +- name: getting value with state=present + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + state: present + register: get_result + +- name: assert set changed and value is correct with state=present + assert: + that: + - set_result.changed == true + - set_result.diff.before == "\n" + - set_result.diff.after == option_value + "\n" + - get_result.changed == false + - get_result.config_value == option_value +... diff --git a/test/integration/targets/git_config/tasks/main.yml b/test/integration/targets/git_config/tasks/main.yml new file mode 100644 index 00000000000..53f62edb446 --- /dev/null +++ b/test/integration/targets/git_config/tasks/main.yml @@ -0,0 +1,23 @@ +--- +# test code for the git_config module + +- name: setup + import_tasks: setup.yml + +- block: + # testing parameters exclusion: state and list_all + - import_tasks: exclusion_state_list-all.yml + # testing get/set option without state + - import_tasks: get_set_no_state.yml + # testing get/set option with state=present + - import_tasks: get_set_state_present.yml + # testing state=absent without value to delete + - import_tasks: unset_no_value.yml + # testing state=absent with value to delete + - import_tasks: unset_value.yml + # testing state=absent with value to delete and a defined value parameter + - import_tasks: precedence_between_unset_and_value.yml + # testing state=absent with check mode + - import_tasks: unset_check_mode.yml + when: git_installed is succeeded and git_version.stdout is version(git_version_supporting_includes, ">=") +... diff --git a/test/integration/targets/git_config/tasks/precedence_between_unset_and_value.yml b/test/integration/targets/git_config/tasks/precedence_between_unset_and_value.yml new file mode 100644 index 00000000000..24ef2920153 --- /dev/null +++ b/test/integration/targets/git_config/tasks/precedence_between_unset_and_value.yml @@ -0,0 +1,25 @@ +--- +- import_tasks: setup_value.yml + +- name: unsetting value + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + state: absent + value: bar + register: unset_result + +- name: getting value + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + register: get_result + +- name: assert unset changed and deleted value + assert: + that: + - unset_result.changed == true + - unset_result.diff.before == option_value + "\n" + - unset_result.diff.after == "\n" + - get_result.config_value == '' +... diff --git a/test/integration/targets/git_config/tasks/setup.yml b/test/integration/targets/git_config/tasks/setup.yml new file mode 100644 index 00000000000..85e168fe65d --- /dev/null +++ b/test/integration/targets/git_config/tasks/setup.yml @@ -0,0 +1,11 @@ +--- +- name: verify that git is installed so this test can continue + command: which git + register: git_installed + ignore_errors: yes + +- name: get git version, only newer than {{git_version_supporting_includes}} has includes option + shell: "git --version | grep 'git version' | sed 's/git version //'" + register: git_version + ignore_errors: yes +... \ No newline at end of file diff --git a/test/integration/targets/git_config/tasks/setup_no_value.yml b/test/integration/targets/git_config/tasks/setup_no_value.yml new file mode 100644 index 00000000000..01a2c9735ed --- /dev/null +++ b/test/integration/targets/git_config/tasks/setup_no_value.yml @@ -0,0 +1,8 @@ +--- +# ------ +# set up : deleting gitconfig file +- name: set up without value + file: + path: ~/.gitconfig + state: absent +... \ No newline at end of file diff --git a/test/integration/targets/git_config/tasks/setup_value.yml b/test/integration/targets/git_config/tasks/setup_value.yml new file mode 100644 index 00000000000..f5e0565441b --- /dev/null +++ b/test/integration/targets/git_config/tasks/setup_value.yml @@ -0,0 +1,8 @@ +--- +# ------ +# set up : set gitconfig with value +- name: set up with value + copy: + src: gitconfig + dest: ~/.gitconfig +... \ No newline at end of file diff --git a/test/integration/targets/git_config/tasks/unset_check_mode.yml b/test/integration/targets/git_config/tasks/unset_check_mode.yml new file mode 100644 index 00000000000..c8fe00c0b7d --- /dev/null +++ b/test/integration/targets/git_config/tasks/unset_check_mode.yml @@ -0,0 +1,25 @@ +--- +- import_tasks: setup_value.yml + +- name: unsetting value with check mode + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + state: absent + check_mode: yes + register: unset_result + +- name: getting value + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + register: get_result + +- name: assert unset changed but dit not delete value + assert: + that: + - unset_result.changed == true + - unset_result.diff.before == option_value + "\n" + - unset_result.diff.after == "\n" + - get_result.config_value == option_value +... diff --git a/test/integration/targets/git_config/tasks/unset_no_value.yml b/test/integration/targets/git_config/tasks/unset_no_value.yml new file mode 100644 index 00000000000..71568e3aa46 --- /dev/null +++ b/test/integration/targets/git_config/tasks/unset_no_value.yml @@ -0,0 +1,23 @@ +--- +- import_tasks: setup_no_value.yml + +- name: unsetting value + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + state: absent + register: unset_result + +- name: getting value + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + register: get_result + +- name: assert unsetting didn't change + assert: + that: + - unset_result.changed == false + - unset_result.msg == 'no setting to unset' + - get_result.config_value == '' +... diff --git a/test/integration/targets/git_config/tasks/unset_value.yml b/test/integration/targets/git_config/tasks/unset_value.yml new file mode 100644 index 00000000000..a2308156aa1 --- /dev/null +++ b/test/integration/targets/git_config/tasks/unset_value.yml @@ -0,0 +1,24 @@ +--- +- import_tasks: setup_value.yml + +- name: unsetting value + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + state: absent + register: unset_result + +- name: getting value + git_config: + name: "{{ option_name }}" + scope: "{{ option_scope }}" + register: get_result + +- name: assert unset changed and deleted value + assert: + that: + - unset_result.changed == true + - unset_result.diff.before == option_value + "\n" + - unset_result.diff.after == "\n" + - get_result.config_value == '' +... diff --git a/test/integration/targets/git_config/vars/main.yml b/test/integration/targets/git_config/vars/main.yml new file mode 100644 index 00000000000..545110a5bff --- /dev/null +++ b/test/integration/targets/git_config/vars/main.yml @@ -0,0 +1,6 @@ +--- +git_version_supporting_includes: 1.7.10 +option_name: http.proxy +option_value: 'foo' +option_scope: global +... \ No newline at end of file