Skip to content

Commit

Permalink
Adding figgy cloud version checks before auto-upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
Jordan Mance committed Sep 19, 2021
1 parent 12594b5 commit a54a3f0
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 28 deletions.
Empty file added REQUIRES_CLOUD_VERSION
Empty file.
2 changes: 1 addition & 1 deletion src/figcli/commands/command_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def instance(self):
optional_args = self._context.find_matching_optional_arguments(help_commands)
context = HelpContext(self._context.resource, self._context.command, optional_args, self._context.run_env,
defaults=self._cli_defaults, role=self._context.role)
factory = HelpFactory(self._context.command, context, self._context)
factory = HelpFactory(self._context.command, context, self._context, self.__config_service())

elif self._context.command in ui_commands or self._context.resource == ui:
context = CommandContext(self._context.run_env, self._context.command, defaults=self._cli_defaults)
Expand Down
23 changes: 15 additions & 8 deletions src/figcli/commands/help/upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from figcli.config import *
from figcli.io.input import Input
from figcli.io.output import Output
from figcli.svcs.config import ConfigService
from figcli.svcs.observability.anonymous_usage_tracker import AnonymousUsageTracker
from figcli.svcs.observability.version_tracker import VersionTracker, FiggyVersionDetails
from figcli.svcs.upgrade_manager import UpgradeManager
Expand All @@ -15,9 +16,9 @@ class Upgrade(HelpCommand):
Drives the --version command
"""

def __init__(self, help_context: HelpContext):
def __init__(self, help_context: HelpContext, config_service: ConfigService):
super().__init__(version, help_context.defaults.colors_enabled, help_context)
self.tracker = VersionTracker(self.context.defaults)
self.tracker = VersionTracker(self.context.defaults, config_service)
self.upgrade_mgr = UpgradeManager(help_context.defaults.colors_enabled)
self._utils = Utils(colors_enabled=help_context.defaults.colors_enabled)
self._out = Output(colors_enabled=help_context.defaults.colors_enabled)
Expand Down Expand Up @@ -49,9 +50,15 @@ def upgrade(self):
if latest_version.version == VERSION:
self._out.success(f'You are currently using the latest version of [[{CLI_NAME}]]: [[{VERSION}]]')
upgrade_it = False

elif self.tracker.upgrade_available(VERSION, latest_version.version):
elif self.tracker.upgrade_available():
self._out.notify_h2(f"New version: [[{latest_version.version}]] is more recent than your version: [[{VERSION}]]")
upgrade_it = True
elif not self.tracker.cloud_version_compatible_with_upgrade():
self._out.notify_h2(f"Version [[{self.tracker.get_version().version}]] of the Figgy CLI is available but your "
f"current version of Figgy Cloud ([[{self.tracker.current_cloud_version()}]]) is not compatible."
f" Your administrator must first update FiggyCloud to at least version: "
f"[[{self.tracker.required_cloud_version()}]] before you can upgrade Figgy.")
upgrade_it = False
else:
self._out.notify_h2(f"Your version: [[{VERSION}]] is more recent then the current recommended version "
f"of {CLI_NAME}: [[{latest_version.version}]]")
Expand All @@ -71,11 +78,11 @@ def upgrade(self):

if install_success:
self._out.success(f"Installation successful! Exiting. Rerun `[[{CLI_NAME}]]` "
f"to use the latest version!")
f"to use the latest version!")
else:
self._out.warn(f"\nUpgrade may not have been successful. Check by re-running "
f"[[`{CLI_NAME}` --version]] to see if it was. If it wasn't, please reinstall [[`{CLI_NAME}`]]. "
f"See {INSTALL_URL}.")
f"[[`{CLI_NAME}` --version]] to see if it was. If it wasn't, please reinstall [[`{CLI_NAME}`]]. "
f"See {INSTALL_URL}.")

def install_mac(self, latest_version: FiggyVersionDetails) -> bool:
install_path = '/usr/local/bin/figgy'
Expand All @@ -95,7 +102,7 @@ def install_mac(self, latest_version: FiggyVersionDetails) -> bool:
return True
else:
self._out.print(f'\n[[Auto-upgrade aborted. To upgrade through brew run:]] \n'
f'-> brew upgrade figtools/figgy/figgy')
f'-> brew upgrade figtools/figgy/figgy')
self._out.warn(f"\n\nYou may continue to manage [[{CLI_NAME}]] through Homebrew, but doing so will "
f"limit some upcoming functionality around canary releases, rollbacks, and dynamic "
f"version-swapping.")
Expand Down
5 changes: 3 additions & 2 deletions src/figcli/commands/help/version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from figcli.commands.help_context import HelpContext
from figcli.commands.types.help import HelpCommand
from figcli.config import *
from figcli.svcs.config import ConfigService
from figcli.svcs.observability.anonymous_usage_tracker import AnonymousUsageTracker
from figcli.svcs.observability.version_tracker import VersionTracker

Expand All @@ -10,9 +11,9 @@ class Version(HelpCommand):
Drives the --version command
"""

def __init__(self, help_context: HelpContext):
def __init__(self, help_context: HelpContext, config_service: ConfigService):
super().__init__(version, help_context.defaults.colors_enabled, help_context)
self.tracker = VersionTracker(self.context.defaults)
self.tracker = VersionTracker(self.context.defaults, config_service)

def version(self):
self.tracker.check_version(self.c)
Expand Down
9 changes: 5 additions & 4 deletions src/figcli/commands/help_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@
from figcli.commands.help_context import HelpContext
from figcli.commands.help.configure import Configure
from figcli.config import *
from figcli.svcs.config import ConfigService
from figcli.svcs.setup import FiggySetup
from figcli.svcs.auth.session_manager import SessionManager
from figcli.utils.utils import Utils, CollectionUtils


class HelpFactory(Factory):
def __init__(self, command: CliCommand, context: HelpContext, figgy_context: FiggyContext):
def __init__(self, command: CliCommand, context: HelpContext, figgy_context: FiggyContext, config: ConfigService):
self._command = command
self._context = context
self._figgy_context = figgy_context
self._options = context.options
self._utils = Utils(False)
self._setup: FiggySetup = FiggySetup(self._figgy_context)
self._config: ConfigService = config

def instance(self):
return self.get(self._command)
Expand All @@ -27,11 +28,11 @@ def get(self, command: CliCommand):
if configure in self._options:
return Configure(self._context, self._setup)
elif version in self._options:
return Version(self._context)
return Version(self._context, self._config)
elif command == login or command == sandbox:
return Login(self._context, self._setup, self._figgy_context)
elif upgrade in self._options:
return Upgrade(self._context)
return Upgrade(self._context, self._config)
else:
self._utils.error_exit(f"{command.name} is not a valid command. You must select from: "
f"[{CollectionUtils.printable_set(help_commands)}]. Try using --help for more info.")
3 changes: 2 additions & 1 deletion src/figcli/config/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pathlib import Path

# If cloud has been updated with required changes, please updated REQUIRES_CLI_VERSION in figtools/figgy
VERSION = '1.2.0'
VERSION = '1.1.0'
CLI_NAME = 'figgy'
PROJECT_NAME = 'figgy'

Expand Down Expand Up @@ -90,6 +90,7 @@
PS_FIGGY_OTS_KEY_ID = '/figgy/kms/figgy-ots-key-id'
PS_FIGGY_ENV_ALIAS = '/figgy/env_alias'
PS_FIGGY_CURRENT_ACCOUNT_ID = '/figgy/account_id'
PS_CLOUD_VERSION_PATH = '/figgy/cloud/version'

# Replication Types:
repl_types = [REPL_TYPE_APP, REPL_TYPE_MERGE]
Expand Down
66 changes: 54 additions & 12 deletions src/figcli/svcs/observability/version_tracker.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Optional, Tuple, Dict

import cachetools.func
import requests
import logging
import random
Expand All @@ -10,7 +12,9 @@
from figcli.config.constants import *
from figcli.config.style.color import Color
from figcli.config.style.terminal_factory import TerminalFactory
from figcli.io.output import Output
from figcli.models.defaults.defaults import CLIDefaults
from figcli.svcs.config import ConfigService

log = logging.getLogger(__name__)

Expand All @@ -19,6 +23,7 @@ class FiggyVersionDetails(BaseModel):
version: str
notify_chance: int
changelog: str
cloud_version_requirement: str

def changes_from(self, old_version: str):
regex = f'.*(##+\s+{self.version}.*)##+\s+{old_version}.*'
Expand All @@ -44,19 +49,23 @@ def from_api_response(response: Dict) -> "FiggyVersionDetails":
return FiggyVersionDetails(
version=version,
notify_chance=int(notify_chance),
changelog=response.get('changelog')
changelog=response.get('changelog'),
cloud_version_requirement=response.get('cloud_version_requirement')
)


class VersionTracker:
_UPGRADE_CHECK_PERCENTAGE = 5 # % chance any decorated method execution will check for an upgrade
_DISABLE_CHECK_ENV_VAR = "FIGGY_DISABLE_VERSION_CHECK"

def __init__(self, cli_defaults: CLIDefaults):
def __init__(self, cli_defaults: CLIDefaults, config_service: ConfigService):
self._cli_defaults = cli_defaults
self.c = TerminalFactory(self._cli_defaults.colors_enabled).instance().get_colors()
self._config = config_service
self._out = Output(colors_enabled=cli_defaults.colors_enabled)

@staticmethod
@cachetools.func.ttl_cache(maxsize=2, ttl=100)
def get_version() -> FiggyVersionDetails:
result = requests.get(FIGGY_GET_VERSION_URL)
if result.status_code == 200:
Expand Down Expand Up @@ -129,24 +138,57 @@ def is_rollback(current_version: str, new_version: str):
except IndentationError:
pass

@staticmethod
def upgrade_available(current_version: str, new_version: str):
def current_version(self):
return VERSION

@cachetools.func.ttl_cache(maxsize=2, ttl=100)
def current_cloud_version(self):
return self._config.get_fig_simple(PS_CLOUD_VERSION_PATH).value

@cachetools.func.ttl_cache(maxsize=2, ttl=100)
def required_cloud_version(self):
details: FiggyVersionDetails = self.get_version()
return details.cloud_version_requirement

def cloud_version_compatible_with_upgrade(self):
req_cloud_version = self.required_cloud_version()
current_cloud_version: str = self.current_cloud_version()

cl_req_major = req_cloud_version.split('.')[0]
cl_req_minor = req_cloud_version.split('.')[1]
cl_req_patch = req_cloud_version.split('.')[2].strip('ab')
cl_cur_major = current_cloud_version.split('.')[0]
cl_cur_minor = current_cloud_version.split('.')[1]
cl_cur_patch = current_cloud_version.split('.')[2].strip('ab')

return cl_cur_major > cl_req_major or \
(cl_cur_major == cl_req_major and cl_cur_minor >= cl_req_minor) or \
(cl_cur_major == cl_req_major and cl_cur_minor == cl_req_minor and cl_cur_patch >= cl_req_patch)

def upgrade_available(self):
try:
current_version = VERSION
details: FiggyVersionDetails = self.get_version()
new_version = details.version

cu_major = current_version.split('.')[0]
cu_minor = current_version.split('.')[1]
cu_patch = current_version.split('.')[2].strip('ab')
new_major = new_version.split('.')[0]
new_minor = new_version.split('.')[1]
new_patch = new_version.split('.')[2].strip('ab')

if new_major > cu_major:
return True
elif new_major >= cu_major and new_minor > cu_minor:
return True
elif new_major >= cu_major and new_minor >= cu_minor and new_patch > cu_patch:
return True
else:
return False
if self.cloud_version_compatible_with_upgrade():
if new_major > cu_major:
return True
elif new_major >= cu_major and new_minor > cu_minor:
return True
elif new_major >= cu_major and new_minor >= cu_minor and new_patch > cu_patch:
return True
else:
return False

return False
except IndentationError:
pass

Expand Down

0 comments on commit a54a3f0

Please sign in to comment.