2015-07-13 14:41:46 +00:00
# (c) 2014, Brian Coca, Josh Drake, et al
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
2015-10-20 01:36:19 +00:00
# Make coding more python3-ish
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
2015-07-13 14:41:46 +00:00
import os
import time
import errno
import codecs
try :
import simplejson as json
except ImportError :
import json
from ansible import constants as C
2015-10-19 18:42:46 +00:00
from ansible . errors import AnsibleError
2016-09-07 05:54:17 +00:00
from ansible . module_utils . _text import to_bytes
2015-07-13 14:41:46 +00:00
from ansible . parsing . utils . jsonify import jsonify
from ansible . plugins . cache . base import BaseCacheModule
2015-10-13 15:14:06 +00:00
2015-11-11 16:38:49 +00:00
try :
from __main__ import display
except ImportError :
from ansible . utils . display import Display
display = Display ( )
2015-07-13 14:41:46 +00:00
class CacheModule ( BaseCacheModule ) :
"""
A caching module backed by json files .
"""
def __init__ ( self , * args , * * kwargs ) :
self . _timeout = float ( C . CACHE_PLUGIN_TIMEOUT )
self . _cache = { }
2016-09-16 19:08:02 +00:00
self . _cache_dir = None
if C . CACHE_PLUGIN_CONNECTION :
# expects a dir path
self . _cache_dir = os . path . expanduser ( os . path . expandvars ( C . CACHE_PLUGIN_CONNECTION ) )
2016-09-14 18:10:12 +00:00
2015-07-13 14:41:46 +00:00
if not self . _cache_dir :
2016-09-14 18:10:12 +00:00
raise AnsibleError ( " error, ' jsonfile ' cache plugin requires the ' fact_caching_connection ' config option to be set (to a writeable directory path) " )
2015-07-13 14:41:46 +00:00
if not os . path . exists ( self . _cache_dir ) :
try :
os . makedirs ( self . _cache_dir )
2015-08-27 06:16:11 +00:00
except ( OSError , IOError ) as e :
2016-09-14 18:10:12 +00:00
display . warning ( " error in ' jsonfile ' cache plugin while trying to create cache dir %s : %s " % ( self . _cache_dir , to_bytes ( e ) ) )
2015-07-13 14:41:46 +00:00
return None
def get ( self , key ) :
2016-05-10 19:54:46 +00:00
""" This checks the in memory cache first as the fact was not expired at ' gather time '
and it would be problematic if the key did expire after some long running tasks and
user gets ' undefined ' error in the same play """
2015-10-17 15:50:14 +00:00
2015-07-13 14:41:46 +00:00
if key in self . _cache :
return self . _cache . get ( key )
2016-05-10 19:54:46 +00:00
if self . has_expired ( key ) or key == " " :
raise KeyError
2015-07-13 14:41:46 +00:00
cachefile = " %s / %s " % ( self . _cache_dir , key )
try :
2015-10-17 15:50:14 +00:00
with codecs . open ( cachefile , ' r ' , encoding = ' utf-8 ' ) as f :
try :
value = json . load ( f )
self . _cache [ key ] = value
return value
except ValueError as e :
2016-09-14 18:10:12 +00:00
display . warning ( " error in ' jsonfile ' cache plugin while trying to read %s : %s . Most likely a corrupt file, so erasing and failing. " % ( cachefile , to_bytes ( e ) ) )
2015-10-17 15:50:14 +00:00
self . delete ( key )
2015-11-11 16:38:49 +00:00
raise AnsibleError ( " The JSON cache file %s was corrupt, or did not otherwise contain valid JSON data. "
" It has been removed, so you can re-run your command now. " % cachefile )
2015-08-27 06:16:11 +00:00
except ( OSError , IOError ) as e :
2016-09-14 18:10:12 +00:00
display . warning ( " error in ' jsonfile ' cache plugin while trying to read %s : %s " % ( cachefile , to_bytes ( e ) ) )
2015-10-17 15:50:14 +00:00
raise KeyError
2015-07-13 14:41:46 +00:00
def set ( self , key , value ) :
self . _cache [ key ] = value
cachefile = " %s / %s " % ( self . _cache_dir , key )
try :
f = codecs . open ( cachefile , ' w ' , encoding = ' utf-8 ' )
2015-08-27 06:16:11 +00:00
except ( OSError , IOError ) as e :
2016-09-14 18:10:12 +00:00
display . warning ( " error in ' jsonfile ' cache plugin while trying to write to %s : %s " % ( cachefile , to_bytes ( e ) ) )
2015-07-13 14:41:46 +00:00
pass
else :
2016-08-08 21:15:19 +00:00
f . write ( jsonify ( value , format = True ) )
2015-07-13 14:41:46 +00:00
finally :
2016-06-14 13:33:45 +00:00
try :
f . close ( )
except UnboundLocalError :
pass
2015-07-13 14:41:46 +00:00
def has_expired ( self , key ) :
2016-09-14 17:27:42 +00:00
if self . _timeout == 0 :
return False
2015-07-13 14:41:46 +00:00
cachefile = " %s / %s " % ( self . _cache_dir , key )
try :
st = os . stat ( cachefile )
2015-08-27 06:16:11 +00:00
except ( OSError , IOError ) as e :
2015-07-13 14:41:46 +00:00
if e . errno == errno . ENOENT :
return False
else :
2016-09-14 18:10:12 +00:00
display . warning ( " error in ' jsonfile ' cache plugin while trying to stat %s : %s " % ( cachefile , to_bytes ( e ) ) )
2015-07-13 14:41:46 +00:00
pass
if time . time ( ) - st . st_mtime < = self . _timeout :
return False
if key in self . _cache :
del self . _cache [ key ]
return True
def keys ( self ) :
keys = [ ]
for k in os . listdir ( self . _cache_dir ) :
if not ( k . startswith ( ' . ' ) or self . has_expired ( k ) ) :
keys . append ( k )
return keys
def contains ( self , key ) :
cachefile = " %s / %s " % ( self . _cache_dir , key )
if key in self . _cache :
return True
if self . has_expired ( key ) :
return False
try :
2015-11-11 16:38:49 +00:00
os . stat ( cachefile )
2015-07-13 14:41:46 +00:00
return True
2015-08-27 06:16:11 +00:00
except ( OSError , IOError ) as e :
2015-07-13 14:41:46 +00:00
if e . errno == errno . ENOENT :
return False
else :
2016-09-14 18:10:12 +00:00
display . warning ( " error in ' jsonfile ' cache plugin while trying to stat %s : %s " % ( cachefile , to_bytes ( e ) ) )
2015-07-13 14:41:46 +00:00
pass
def delete ( self , key ) :
2015-10-12 17:03:49 +00:00
try :
del self . _cache [ key ]
except KeyError :
pass
2015-07-13 14:41:46 +00:00
try :
os . remove ( " %s / %s " % ( self . _cache_dir , key ) )
2015-10-19 18:42:46 +00:00
except ( OSError , IOError ) :
2015-07-13 14:41:46 +00:00
pass #TODO: only pass on non existing?
def flush ( self ) :
self . _cache = { }
for key in self . keys ( ) :
self . delete ( key )
def copy ( self ) :
ret = dict ( )
for key in self . keys ( ) :
ret [ key ] = self . get ( key )
return ret