-
Notifications
You must be signed in to change notification settings - Fork 23.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: New module: diff (string, file or command output vs string, file or command output) #28469
Conversation
The test
The test
The test
The test
The test
The test
|
The test
The test
|
@EvanK @ahtik @astorije @bendoh @bpennypacker @cmprescott @dhozac @jhoekx @jirutka @jpmens @luisperlaz @noseka1 @pileofrogs @ribbons @sfromm @sm4rk0 @tbielawa @tima @yaegashi As a maintainer of a module in the same namespace this new module has been submitted to, your vote counts for shipits. Please review this module and add |
Weird, I proposed this module less than 24h ago on #ansible-devel, and implemented my own version. The version I propose is pure-python. Update: I made a PR of my version at: #28475 Your module does more than I needed, I only needed to compare a file to another file (or string). and I wanted it to be pure python (so you could compare remotely on systems that have python, but may not have diff) using difflib. Maybe you can integrate this into your module ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I would change the user-interface of the module so that you can do e.g.:
- diff:
src_file: /foo/bar
dest_string: |
foo: bar
foob: blab
- diff:
src_cmd: iptables -L -xnv
dest_file: iptables-output.txt
I would also implement remote_src
so that the user can decide whether a src file or command originates from the control machine, or the remote node.
lib/ansible/modules/files/diff.py
Outdated
DOCUMENTATION = ''' | ||
--- | ||
module: diff | ||
author: cytopia (@cytopia) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The author part is a list now, so it should be:
author:
- cytopia (@cytopia)
lib/ansible/modules/files/diff.py
Outdated
|
||
# (c) 2017, cytopia <cytopia@everythingcli.org> | ||
# | ||
# This module is free software: you can redistribute it and/or modify |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now have a one-line GPL header that we use:
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
lib/ansible/modules/files/diff.py
Outdated
description: | ||
- The source input to diff. Can be a string, contents of a file or output from a command, depending on I(source_type). | ||
required: true | ||
default: null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't add default: null
, that's implicit and adds no value. (Especially since it is a required parameter)
lib/ansible/modules/files/diff.py
Outdated
description: | ||
- The target input to diff. Can be a string, contents of a file or output from a command, depending on I(target_type). | ||
required: true | ||
default: null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't add default: null
, that's implicit and adds no value. (Especially since it is a required parameter)
- More examples at U(https://github.com/cytopia/ansible-modules) | ||
version_added: "2.4" | ||
options: | ||
source: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not typically have a parameter that can be one of many things. Mostly because it makes the argument_spec hard to design. What you should be doing here is make different (mutual exclusive) parameters for a string, file or command using mutual_exclusive and required_together. and then you don't need a type selector parameter, the used options will make clear what the user wants.
lib/ansible/modules/files/diff.py
Outdated
- The source input to diff. Can be a string, contents of a file or output from a command, depending on I(source_type). | ||
required: true | ||
default: null | ||
aliases: [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty aliases is not needed. Keep it simple.
lib/ansible/modules/files/diff.py
Outdated
- The target input to diff. Can be a string, contents of a file or output from a command, depending on I(target_type). | ||
required: true | ||
default: null | ||
aliases: [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty aliases is not needed. Keep it simple.
lib/ansible/modules/files/diff.py
Outdated
default: null | ||
aliases: [] | ||
|
||
source_type: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I would get rid of these.
@dagwieers thanks for the review. I will take this into account. |
I understand, however then it might also get messy: module = AnsibleModule(
argument_spec=dict(
src_string=dict(required=False, default=None, type='str'),
src_file_remote=dict(required=False, default=None, type='path'),
src_file_locale=dict(required=False, default=None, type='path'),
src_cmd_remote=dict(required=False, default=None, type='str'),
src_cmd_local=dict(required=False, default=None, type='str'),
dst_string=dict(required=False, default=None, type='str'),
dst_file_remote=dict(required=False, default=None, type='path'),
dst_file_locale=dict(required=False, default=None, type='path'),
dst_cmd_remote=dict(required=False, default=None, type='str'),
dst_cmd_local=dict(required=False, default=None, type='str')
),
supports_check_mode=True,
mutually_exclusive=[
['src_string', 'src_file_remote', 'src_file_local', 'src_cmd_remote', 'src_cmd_local'],
['dst_string', 'dst_file_remote', 'dst_file_local', 'dst_cmd_remote', 'dst_cmd_local']
]
) (btw, how would I do the As opposed to the following, which would allow to easily add more options wihtout introducing more parameters: module = AnsibleModule(
argument_spec=dict(
source=dict(required=True, default=None, type='str'),
target=dict(required=True, default=None, type='str'),
source_type=dict(required=True, default='string',
choices=['string', 'local_file', 'remote_file',
'local_cmd', 'remote_cmd']),
target_type=dict(required=True, default='string',
choices=['string', 'local_file', 'remote_file',
'local_cmd', 'remote_cmd']),
),
supports_check_mode=True
) What do you think? |
You don't need local/remote, the option remote_src exists in various Ansible modules already. And you don't need to support the case where both files are local (because why not run it on localhost in that case). You have 3 options for both src and dest. string, file and command (although you could only support string and file and leave command to Ansible tasks). And only src could come from the control host (and by default the src is always to be expected to come from the control host) So in the most simple case, I would go for: module = AnsibleModule(
argument_spec=dict(
src_string=dict(type='str', aliases=['content']),
src_file=dict(type='path', aliases=['src']),
dest_string=dict(type='str'),
dest_file=dict(type='path', aliases=['dest']),
remote_src=dict(type='bool', default=False),
),
supports_check_mode=True,
mutually_exclusive=[
['src_file', 'src_string'],
['dest_file', 'dest_string'],
],
required_one_of=[
['src_file', 'src_string'],
['dest_file', 'dest_string'],
],
) Beware that:
|
@dagwieers the whole point of this PR was that I will be able to Introducing your changes for local and remote support: Now, I could go further and add support for both, local and remote support (but not remove command output support, as this is what I require). This would allow for 25 different combinations: Where
So I do need those parameters: src_string=dict(required=False, default=None, type='str'),
src_file_remote=dict(required=False, default=None, type='path'),
src_file_locale=dict(required=False, default=None, type='path'),
src_cmd_remote=dict(required=False, default=None, type='str'),
src_cmd_local=dict(required=False, default=None, type='str'),
dst_string=dict(required=False, default=None, type='str'),
dst_file_remote=dict(required=False, default=None, type='path'),
dst_file_locale=dict(required=False, default=None, type='path'),
dst_cmd_remote=dict(required=False, default=None, type='str'),
dst_cmd_local=dict(required=False, default=None, type='str') They can also be renamed. From what you have posted, I see that you are using the terms So I guess proper naming must be defined that it allows all 25 combinations listed above. The only two questions I still have is:
|
How can I execute a command locally or remotely? - command: echo Remote command
register: remote_cmd
- command: echo Local command as root
register: local_root_cmd
become_user: root
delegate_to: localhost
- diff:
src_string: '{{ remote_cmd.stdout }}'
dest_string: '{{ local_root_cmd.stdout }}' NOTE: The command could be run on a second host, or a reference host, it doesn't really matter because you are passing strings. Any string you like. And this makes it possible to also compare stderr, if you like. How can I read a file from either local or remote? - name: Compare local and remote file
diff:
src: /some/local/file.txt
dest: /some/remote/file.txt
- name: Compare 2 remote files
diff:
src: /some/remote/file1.txt
dest: /some/remote/file2.txt
remote_src: yes
- name: Compare 2 local files:
diff:
src: /some/local/file1.txt
dest: /some/local/file2.txt
delegate_to: localhost How to compare a command to an expected string? - command: echo Foobar
register: foobar
- diff:
src_string: Foobar
dest_string: '{{ foobar.stdout }}'
- diff:
content: foobar.ansible.com
dest: /etc/hostname |
it seems simpler to have a filter or lookup that does this, in it's simplest form: but then you can use variables and/or lookups to deal with remote/local files: but for files, in most cases i would just use 'copy' in diff mode, with the 'content' option for when you have a string when running with - copy: src=/path/file1 dest=/path/file2 Soon we should have 'per task' |
What about the case I want to view the difference for two remote files? Such as:
My above suggestion (with many cases) would cover that as well. |
A diff filter still covers that case as you can fetch/slurp both files |
@cytopia Your branch does not contain a shippable.yml file. Please rebase your branch to trigger running of current tests. |
@cytopia Given that:
Therefore I'm going to close this. If you or anyone else wants to continue with this work then please do feel free to create a fresh PR and |
SUMMARY
This module lets you
diff
compare a string, a file or a command output against any combination of another string, file or command output. If the output differs (changes are present), then Ansible will report its state aschanged
, otherwise not.Check mode is supported, except when diffing a command. In this case it will automatically skip the task with a custom skip message.
ISSUE TYPE
COMPONENT NAME
diff
ANSIBLE VERSION
ADDITIONAL INFORMATION
For reference see here: https://github.com/cytopia/ansible-modules