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/>.
import os
import time
import errno
import codecs
try :
import simplejson as json
except ImportError :
import json
from ansible import constants as C
from ansible . errors import *
from ansible . parsing . utils . jsonify import jsonify
from ansible . plugins . cache . base import BaseCacheModule
2015-10-13 15:14:06 +00:00
from ansible . utils . unicode import to_bytes
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 = { }
self . _cache_dir = C . CACHE_PLUGIN_CONNECTION # expects a dir path
if not self . _cache_dir :
raise AnsibleError ( " error, fact_caching_connection is not set, cannot use fact cache " )
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 :
2015-10-13 15:14:06 +00:00
self . _display . warning ( " error 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 ) :
if key in self . _cache :
return self . _cache . get ( key )
if self . has_expired ( key ) :
raise KeyError
cachefile = " %s / %s " % ( self . _cache_dir , key )
try :
f = codecs . open ( cachefile , ' r ' , encoding = ' utf-8 ' )
2015-08-27 06:16:11 +00:00
except ( OSError , IOError ) as e :
2015-10-13 15:14:06 +00:00
self . _display . warning ( " error while trying to read %s : %s " % ( cachefile , to_bytes ( e ) ) )
2015-07-13 14:41:46 +00:00
pass
else :
try :
value = json . load ( f )
self . _cache [ key ] = value
return value
2015-10-12 17:03:49 +00:00
except ValueError as e :
2015-10-13 15:14:06 +00:00
self . _display . warning ( " error while trying to read %s : %s . Most likely a corrupt file, so erasing and failing. " % ( cachefile , to_bytes ( e ) ) )
2015-10-12 17:03:49 +00:00
self . delete ( key )
2015-10-13 06:33:18 +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-07-13 14:41:46 +00:00
finally :
f . close ( )
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 :
2015-10-13 15:14:06 +00:00
self . _display . warning ( " error while trying to write to %s : %s " % ( cachefile , to_bytes ( e ) ) )
2015-07-13 14:41:46 +00:00
pass
else :
f . write ( jsonify ( value ) )
finally :
f . close ( )
def has_expired ( self , key ) :
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 :
2015-10-13 15:14:06 +00:00
self . _display . warning ( " error 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 :
st = os . stat ( cachefile )
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 :
2015-10-13 15:14:06 +00:00
self . _display . warning ( " error 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-08-27 06:16:11 +00:00
except ( OSError , IOError ) as e :
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