diff --git a/endpoints/common.py b/endpoints/common.py
index c2734031e..bbbed4754 100644
--- a/endpoints/common.py
+++ b/endpoints/common.py
@@ -231,6 +231,7 @@ def render_page_template(name, route_data=None, **kwargs):
preferred_scheme=app.config['PREFERRED_URL_SCHEME'],
version_number=version_number,
license_insufficient=license_validator.insufficient,
+ license_expiring=license_validator.expiring_soon,
**kwargs))
resp.headers['X-FRAME-OPTIONS'] = 'DENY'
diff --git a/templates/base.html b/templates/base.html
index a88780f0b..8db50f14d 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -240,6 +240,13 @@ mixpanel.init("{{ mixpanel_key }}", { track_pageview : false, debug: {{ is_debug
+ {% if not has_billing and license_expiring %}
+
+ The Quay Enterprise license will expire shortly. Please contact your administrator to avoid
+ service disruption.
+
+ {% endif %}
+
{% if license_insufficient %}
The Quay Enterprise license has expired or is insufficient for this installation. Please contact your administrator.
diff --git a/util/license.py b/util/license.py
index f9622cddd..57def018f 100644
--- a/util/license.py
+++ b/util/license.py
@@ -21,9 +21,12 @@ import jwt
logger = logging.getLogger(__name__)
-TRIAL_GRACE_PERIOD = timedelta(7, 0) # 1 week
-MONTHLY_GRACE_PERIOD = timedelta(335, 0) # 11 months
-YEARLY_GRACE_PERIOD = timedelta(90, 0) # 3 months
+TRIAL_GRACE_PERIOD = timedelta(days=7) # 1 week
+MONTHLY_GRACE_PERIOD = timedelta(days=335) # 11 months
+YEARLY_GRACE_PERIOD = timedelta(days=90) # 3 months
+
+LICENSE_SOON_DELTA = timedelta(days=7) # 1 week
+
LICENSE_FILENAME = 'license'
QUAY_ENTITLEMENT = 'software.quay'
@@ -338,10 +341,16 @@ class LicenseValidator(Thread):
# multiprocessing.Value does not ensure consistent write-after-reads, but we don't need that.
self._license_is_insufficient = multiprocessing.Value(c_bool, True)
+ self._license_expiring_soon = multiprocessing.Value(c_bool, True)
super(LicenseValidator, self).__init__(*args, **kwargs)
self.daemon = True
+ @property
+ def expiring_soon(self):
+ """ Returns whether the license will be expiring soon (a week from now). """
+ return self._license_expiring_soon.value
+
@property
def insufficient(self):
return self._license_is_insufficient.value
@@ -354,14 +363,20 @@ class LicenseValidator(Thread):
try:
current_license = self._config_provider.get_license()
now = datetime.now()
+ soon = now + LICENSE_SOON_DELTA
any_invalid = not all(current_license.validate_entitlement_requirement(req, now).is_met()
for req in self._entitlement_requirements)
+ soon_invalid = not all(current_license.validate_entitlement_requirement(req, soon).is_met()
+ for req in self._entitlement_requirements)
logger.debug('updating license license_is_insufficient to %s', any_invalid)
+ logger.debug('updating license license_expiring_soon to %s', soon_invalid)
except (IOError, LicenseDecodeError):
logger.exception('failed to validate license')
any_invalid = True
+ soon_invalid = False
self._license_is_insufficient.value = any_invalid
+ self._license_expiring_soon.value = soon_invalid
return any_invalid
def run(self):