diff --git a/juju/client/connection.py b/juju/client/connection.py index 3d680175..ff6d8ee6 100644 --- a/juju/client/connection.py +++ b/juju/client/connection.py @@ -17,7 +17,7 @@ from juju import errors, tag, utils, jasyncio from juju.client import client from juju.utils import IdQueue -from juju.version import TARGET_JUJU_VERSION +from juju.version import CLIENT_VERSION log = logging.getLogger('juju.client.connection') @@ -963,7 +963,7 @@ def _build_facades(self, facades_from_connection): async def login(self): params = {} # Set the client version - params['client-version'] = TARGET_JUJU_VERSION + params['client-version'] = CLIENT_VERSION params['auth-tag'] = self.usertag if self.password: params['credentials'] = self.password diff --git a/juju/client/connector.py b/juju/client/connector.py index 69840620..f1fe54a5 100644 --- a/juju/client/connector.py +++ b/juju/client/connector.py @@ -4,6 +4,8 @@ import copy import logging +from packaging import version + import macaroonbakery.httpbakery as httpbakery from juju.client import client @@ -11,8 +13,8 @@ from juju.client.gocookies import GoCookieJar, go_to_py_cookie from juju.client.jujudata import API_ENDPOINTS_KEY, FileJujuData from juju.client.proxy.factory import proxy_from_config -from juju.errors import JujuConnectionError, JujuError -from juju.version import SUPPORTED_MAJOR_VERSION, TARGET_JUJU_VERSION +from juju.errors import JujuConnectionError, JujuError, JujuUnknownVersion +from juju.version import CLIENT_VERSION log = logging.getLogger("connector") @@ -86,16 +88,20 @@ async def connect(self, **kwargs): self._connection = await Connection.connect(**kwargs) # Check if we support the target controller - juju_server_version = self._connection.info["server-version"] - if not juju_server_version.startswith(TARGET_JUJU_VERSION): - log.debug( - "This version was tested using {} juju version {} may have compatibility issues".format( - TARGET_JUJU_VERSION, juju_server_version - ) - ) - if not self._connection.info["server-version"].startswith( - SUPPORTED_MAJOR_VERSION - ): + server_version = self._connection.info["server-version"] + try: + juju_server_version = version.parse(server_version) + except version.InvalidVersion as err: + # We're only interested in the major version, so + # we attempt to clean up versions such as 3.4-rc1.2 as just 3.4 + if '-' not in server_version: + raise JujuUnknownVersion(err) + juju_server_version = version.parse(server_version.split('-')[0]) + + # CLIENT_VERSION statically comes from the VERSION file in the repo + client_version = version.parse(CLIENT_VERSION) + + if juju_server_version.major != client_version.major: raise JujuConnectionError( "juju server-version %s not supported" % juju_server_version ) diff --git a/juju/errors.py b/juju/errors.py index b996ea7f..d0284158 100644 --- a/juju/errors.py +++ b/juju/errors.py @@ -137,3 +137,7 @@ class AbstractMethodError(Exception): class PylibjujuError(JujuError): pass + + +class JujuUnknownVersion(PylibjujuError): + pass diff --git a/juju/version.py b/juju/version.py index 3035fce9..e4289b46 100644 --- a/juju/version.py +++ b/juju/version.py @@ -1,16 +1,19 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import pathlib +import re LTS_RELEASES = ["jammy", "focal", "bionic", "xenial", "trusty", "precise"] DEFAULT_ARCHITECTURE = 'amd64' -# Juju server version we target. Depending on this value, the Juju server -# may stop the connecting considering us not compatible. -TARGET_JUJU_VERSION = '3.2.0' - -# Used by connector to determine if we are compatible with the juju server -SUPPORTED_MAJOR_VERSION = '3' - -SUPPORTED_MAJOR_MINOR_VERSION = '3.2' +# CLIENT_VERSION (that's read from the VERSION file) is the highest Juju server +# version that this client supports. +# Note that this is a ceiling. CLIENT_VERSION <= juju-controller-version works. +# For CLIENT_VERSION < juju-controller-version (strictly smaller), we emit a warning +# to update the client to the latest. +# However, for any CLIENT_VERSION > juju-controller-version, a "client incompatible +# with server" will be returned by the juju controller. +VERSION_FILE_PATH = pathlib.Path(__file__).parent.parent / 'VERSION' +CLIENT_VERSION = re.search(r'\d+\.\d+\.\d+', open(VERSION_FILE_PATH).read().strip()).group()