
The __future__ module [1] was used in this context to ensure compatibility between python 2 and python 3. We previously dropped the support of python 2.7 [2] and now we only support python 3 so we don't need to continue to use this module and the imports listed below. Imports commonly used and their related PEPs: - `division` is related to PEP 238 [3] - `print_function` is related to PEP 3105 [4] - `unicode_literals` is related to PEP 3112 [5] - `with_statement` is related to PEP 343 [6] - `absolute_import` is related to PEP 328 [7] [1] https://docs.python.org/3/library/__future__.html [2] https://governance.openstack.org/tc/goals/selected/ussuri/drop-py27.html [3] https://www.python.org/dev/peps/pep-0238 [4] https://www.python.org/dev/peps/pep-3105 [5] https://www.python.org/dev/peps/pep-3112 [6] https://www.python.org/dev/peps/pep-0343 [7] https://www.python.org/dev/peps/pep-0328 Change-Id: Ia36b422b748a9b06f40a15d74c6b56b508f2a771
165 lines
4.8 KiB
Python
165 lines
4.8 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright 2019 Red Hat, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
__metaclass__ = type
|
|
|
|
from ansible.errors import AnsibleActionFail
|
|
from ansible.errors import AnsibleActionSkip
|
|
from ansible.module_utils.parsing.convert_bool import boolean
|
|
from ansible.plugins.action import ActionBase
|
|
from datetime import datetime
|
|
|
|
import yaml
|
|
|
|
ANSIBLE_METADATA = {
|
|
'metadata_version': '1.1',
|
|
'status': ['preview'],
|
|
'supported_by': 'community'
|
|
}
|
|
|
|
DOCUMENTATION = """
|
|
module: timestamp_file
|
|
author:
|
|
- "Alex Schultz (@mwhahaha)"
|
|
version_added: '2.9'
|
|
short_description: Take a copy of a file and append a timestamp
|
|
notes: []
|
|
description:
|
|
- Take a copy of a file and append a timestamp
|
|
requirements:
|
|
- None
|
|
options:
|
|
path:
|
|
description:
|
|
- Path to file
|
|
type: str
|
|
remove:
|
|
description:
|
|
- Remove original file
|
|
default: False
|
|
type: bool
|
|
force:
|
|
description:
|
|
- Overwrite destination file if it exists
|
|
default: False
|
|
type: bool
|
|
date_format:
|
|
description:
|
|
- Timestamp format to use when appending to destination file
|
|
default: "%Y-%m-%d_%H:%M:%S"
|
|
type: str
|
|
"""
|
|
EXAMPLES = """
|
|
- name: Snapshot a file
|
|
timestamp_file:
|
|
path: /tmp/file.log
|
|
- name: Snapshot a file and remove original
|
|
timestamp_file:
|
|
path: /tmp/file.log
|
|
remove: True
|
|
"""
|
|
RETURN = """
|
|
dest:
|
|
description: Path to the new file
|
|
returned: if changed
|
|
type: str
|
|
sample: "/tmp/file.log.2017-07-27_16:39:00"
|
|
"""
|
|
|
|
|
|
class ActionModule(ActionBase):
|
|
|
|
_VALID_ARGS = yaml.safe_load(DOCUMENTATION)['options']
|
|
|
|
def _get_args(self):
|
|
missing = []
|
|
args = {}
|
|
|
|
for option, vals in self._VALID_ARGS.items():
|
|
if 'default' not in vals:
|
|
if self._task.args.get(option, None) is None:
|
|
missing.append(option)
|
|
continue
|
|
args[option] = self._task.args.get(option)
|
|
else:
|
|
args[option] = self._task.args.get(option, vals['default'])
|
|
|
|
if missing:
|
|
raise AnsibleActionFail('Missing required parameters: {}'.format(
|
|
', '.join(missing)))
|
|
return args
|
|
|
|
def _get_date_string(self, date_format):
|
|
return datetime.now().strftime(date_format)
|
|
|
|
def run(self, tmp=None, task_vars=None):
|
|
if task_vars is None:
|
|
task_vars = dict()
|
|
result = super(ActionModule, self).run(tmp, task_vars)
|
|
del tmp
|
|
# parse args
|
|
args = self._get_args()
|
|
|
|
changed = False
|
|
src_path = args['path']
|
|
|
|
# check if source file exists
|
|
file_stat = self._execute_module(
|
|
module_name='stat',
|
|
module_args=dict(path=src_path),
|
|
task_vars=task_vars
|
|
)
|
|
timestamp = self._get_date_string(args['date_format'])
|
|
dest_path = '.'.join([src_path, timestamp])
|
|
if file_stat.get('stat', {}).get('exists', False) is False:
|
|
# file doesn't exist so we're done
|
|
raise AnsibleActionSkip("{} does not exist.".format(src_path))
|
|
|
|
# check if destination file exists
|
|
file_stat = self._execute_module(
|
|
module_name='stat',
|
|
module_args=dict(path=dest_path),
|
|
task_vars=task_vars
|
|
)
|
|
if (not args['force']
|
|
and file_stat.get('stat', {}).get('exists', False) is True):
|
|
raise AnsibleActionFail("Destination file {} exists. Use force "
|
|
"option to proceed.".format(dest_path))
|
|
|
|
# copy file out of the way
|
|
copy_result = self._execute_module(
|
|
module_name='copy',
|
|
module_args=dict(src=src_path, dest=dest_path, remote_src=True),
|
|
task_vars=task_vars
|
|
)
|
|
if copy_result.get('failed', False):
|
|
return copy_result
|
|
changed = True
|
|
|
|
if boolean(args.get('remove', False), strict=False):
|
|
# cleanup original file as requested
|
|
file_result = self._execute_module(
|
|
module_name='file',
|
|
module_args=dict(path=src_path, state='absent'),
|
|
task_vars=task_vars
|
|
)
|
|
if file_result.get('failed', False):
|
|
return file_result
|
|
|
|
result['dest'] = copy_result['dest']
|
|
result['changed'] = changed
|
|
return result
|