From 0294f7d29c2660981b03ce86320107c2f0053f93 Mon Sep 17 00:00:00 2001 From: Ben Hoyt Date: Tue, 27 Jun 2023 11:39:14 +1200 Subject: [PATCH 1/8] Add support for exec "context" This is Pebble PR https://github.com/canonical/pebble/pull/246 Also add working_dir field to pebble.Service and flesh out types for HttpDict, TcpDict, and ExecDict. --- ops/model.py | 4 ++++ ops/pebble.py | 33 ++++++++++++++++++++++++++++++--- test/pebble_cli.py | 2 ++ test/test_model.py | 2 ++ test/test_pebble.py | 6 +++++- 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/ops/model.py b/ops/model.py index 3458c77c7..11e2255fb 100644 --- a/ops/model.py +++ b/ops/model.py @@ -2326,6 +2326,7 @@ def exec( # noqa self, command: List[str], *, + context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2347,6 +2348,7 @@ def exec( # noqa self, command: List[str], *, + context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2366,6 +2368,7 @@ def exec( self, command: List[str], *, + context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2386,6 +2389,7 @@ def exec( """ return self._pebble.exec( command, + context=context, environment=environment, working_dir=working_dir, timeout=timeout, diff --git a/ops/pebble.py b/ops/pebble.py index 21ad199b1..d769691db 100644 --- a/ops/pebble.py +++ b/ops/pebble.py @@ -79,6 +79,7 @@ 'user-id': Optional[int], 'group': str, 'group-id': Optional[int], + 'working-dir': str, 'on-success': str, 'on-failure': str, 'on-check-failure': Dict[str, Any], @@ -88,9 +89,24 @@ }, total=False) -HttpDict = typing.TypedDict('HttpDict', {'url': str}) -TcpDict = typing.TypedDict('TcpDict', {'port': int}) -ExecDict = typing.TypedDict('ExecDict', {'command': str}) +HttpDict = typing.TypedDict('HttpDict', + {'url': str, + 'headers': Dict[str, str]}, + total=False) +TcpDict = typing.TypedDict('TcpDict', + {'port': int, + 'host': str}, + total=False) +ExecDict = typing.TypedDict('ExecDict', + {'command': str, + 'context': str, + 'environment': Dict[str, str], + 'user-id': Optional[int], + 'user': str, + 'group-id': Optional[int], + 'group': str, + 'working-dir': str}, + total=False) CheckDict = typing.TypedDict('CheckDict', {'override': str, @@ -771,6 +787,7 @@ def __init__(self, name: str, raw: Optional['ServiceDict'] = None): self.user_id = dct.get('user-id') self.group = dct.get('group', '') self.group_id = dct.get('group-id') + self.working_dir = dct.get('working-dir', '') self.on_success = dct.get('on-success', '') self.on_failure = dct.get('on-failure', '') self.on_check_failure = dict(dct.get('on-check-failure', {})) @@ -794,6 +811,7 @@ def to_dict(self) -> 'ServiceDict': ('user-id', self.user_id), ('group', self.group), ('group-id', self.group_id), + ('working-dir', self.working_dir), ('on-success', self.on_success), ('on-failure', self.on_failure), ('on-check-failure', self.on_check_failure), @@ -2051,6 +2069,7 @@ def exec( # noqa self, command: List[str], *, + context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2072,6 +2091,7 @@ def exec( # noqa self, command: List[str], *, + context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2091,6 +2111,7 @@ def exec( self, command: List[str], *, + context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2170,6 +2191,11 @@ def exec( Args: command: Command to execute: the first item is the name (or path) of the executable, the rest of the items are the arguments. + context: If specified, run the command in the context of this + service, that is, inherit its environment variables, + user/group settings, and working directory. The other exec + options will override the service context; ``environment`` + will be merged on top of the service's. environment: Environment variables to pass to the process. working_dir: Working directory to run the command in. If not set, Pebble uses the target user's $HOME directory (and if the user @@ -2235,6 +2261,7 @@ def exec( body = { 'command': command, + 'context': context, 'environment': environment or {}, 'working-dir': working_dir, 'timeout': _format_timeout(timeout) if timeout is not None else None, diff --git a/test/pebble_cli.py b/test/pebble_cli.py index 05fc42265..a5e13dd1c 100644 --- a/test/pebble_cli.py +++ b/test/pebble_cli.py @@ -64,6 +64,7 @@ def main(): p.add_argument('name', help='check name(s) to filter on', nargs='*') p = subparsers.add_parser('exec', help='execute a command') + p.add_argument('--context', help='service context') p.add_argument('--env', help='environment variables to set', action='append', metavar='KEY=VALUE') p.add_argument('--working-dir', help='working directory to run command in') @@ -196,6 +197,7 @@ def main(): process = client.exec( args.exec_command, + context=args.context, environment=environment, working_dir=args.working_dir, timeout=args.timeout, diff --git a/test/test_model.py b/test/test_model.py index 97b0c9ca4..87bfeb268 100755 --- a/test/test_model.py +++ b/test/test_model.py @@ -1781,6 +1781,7 @@ def test_exec(self): self.pebble.responses.append('fake_exec_process') p = self.container.exec( ['echo', 'foo'], + context='srv1', environment={'K1': 'V1', 'K2': 'V2'}, working_dir='WD', timeout=10.5, @@ -1796,6 +1797,7 @@ def test_exec(self): ) self.assertEqual(self.pebble.requests, [ ('exec', ['echo', 'foo'], dict( + context='srv1', environment={'K1': 'V1', 'K2': 'V2'}, working_dir='WD', timeout=10.5, diff --git a/test/test_pebble.py b/test/test_pebble.py index 586b5f0bb..e5ba3ec0e 100644 --- a/test/test_pebble.py +++ b/test/test_pebble.py @@ -681,6 +681,7 @@ def _assert_empty(self, service, name): self.assertIs(service.user_id, None) self.assertEqual(service.group, '') self.assertIs(service.group_id, None) + self.assertEqual(service.working_dir, '') self.assertEqual(service.on_success, '') self.assertEqual(service.on_failure, '') self.assertEqual(service.on_check_failure, {}) @@ -711,6 +712,7 @@ def test_dict(self): 'user-id': 1000, 'group': 'staff', 'group-id': 2000, + 'working-dir': '/working/dir', 'on-success': 'restart', 'on-failure': 'ignore', 'on-check-failure': {'chk1': 'halt'}, @@ -732,6 +734,7 @@ def test_dict(self): self.assertEqual(s.user_id, 1000) self.assertEqual(s.group, 'staff') self.assertEqual(s.group_id, 2000) + self.assertEqual(s.working_dir, '/working/dir') self.assertEqual(s.on_success, 'restart') self.assertEqual(s.on_failure, 'ignore') self.assertEqual(s.on_check_failure, {'chk1': 'halt'}) @@ -2639,10 +2642,11 @@ def add_responses(self, change_id, exit_code, change_err=None): return (stdio, stderr, control) def build_exec_data( - self, command, environment=None, working_dir=None, timeout=None, + self, command, context=None, environment=None, working_dir=None, timeout=None, user_id=None, user=None, group_id=None, group=None, combine_stderr=False): return { 'command': command, + 'context': context, 'environment': environment or {}, 'working-dir': working_dir, 'timeout': f'{timeout:.3f}s' if timeout is not None else None, From 8f68a98d2565668a7e0f1a4c952188b48c80d979 Mon Sep 17 00:00:00 2001 From: Ben Hoyt Date: Tue, 27 Jun 2023 12:58:36 +1200 Subject: [PATCH 2/8] Comment out grafana-k8s-operator CI till type checking issue is fixed See https://github.com/canonical/grafana-k8s-operator/pull/223 --- .github/workflows/observability-charm-tests.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/observability-charm-tests.yaml b/.github/workflows/observability-charm-tests.yaml index 7a66f6b6f..0c2dc401f 100644 --- a/.github/workflows/observability-charm-tests.yaml +++ b/.github/workflows/observability-charm-tests.yaml @@ -12,7 +12,8 @@ jobs: charm-repo: - "canonical/alertmanager-k8s-operator" - "canonical/prometheus-k8s-operator" - - "canonical/grafana-k8s-operator" +# TODO(benhoyt): uncomment when https://github.com/canonical/grafana-k8s-operator/pull/223 is fixed +# - "canonical/grafana-k8s-operator" steps: - name: Checkout the ${{ matrix.charm-repo }} repository From 31926c4a38f55de3c54fe39c26746929c6de2fcd Mon Sep 17 00:00:00 2001 From: Ben Hoyt Date: Tue, 27 Jun 2023 12:58:45 +1200 Subject: [PATCH 3/8] Bump up to latest Pyright version while we're at it --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 320fe0324..0b2ac7faa 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ flake8-builtins==2.1.0 pyproject-flake8==4.0.1 pep8-naming==0.13.2 pytest==7.2.1 -pyright==1.1.313 +pyright==1.1.316 pytest-operator==0.23.0 coverage[toml]==7.0.5 typing_extensions==4.2.0 From 7561a98dc22040c90bf00a8f419ba132f4ad6bfa Mon Sep 17 00:00:00 2001 From: Ben Hoyt Date: Wed, 28 Jun 2023 14:32:33 +1200 Subject: [PATCH 4/8] Wording tweak; undo commented-out CI test --- .github/workflows/observability-charm-tests.yaml | 3 +-- ops/pebble.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/observability-charm-tests.yaml b/.github/workflows/observability-charm-tests.yaml index 0c2dc401f..7a66f6b6f 100644 --- a/.github/workflows/observability-charm-tests.yaml +++ b/.github/workflows/observability-charm-tests.yaml @@ -12,8 +12,7 @@ jobs: charm-repo: - "canonical/alertmanager-k8s-operator" - "canonical/prometheus-k8s-operator" -# TODO(benhoyt): uncomment when https://github.com/canonical/grafana-k8s-operator/pull/223 is fixed -# - "canonical/grafana-k8s-operator" + - "canonical/grafana-k8s-operator" steps: - name: Checkout the ${{ matrix.charm-repo }} repository diff --git a/ops/pebble.py b/ops/pebble.py index d769691db..8ced3c47f 100644 --- a/ops/pebble.py +++ b/ops/pebble.py @@ -2192,7 +2192,7 @@ def exec( command: Command to execute: the first item is the name (or path) of the executable, the rest of the items are the arguments. context: If specified, run the command in the context of this - service, that is, inherit its environment variables, + service. Specifically, inherit its environment variables, user/group settings, and working directory. The other exec options will override the service context; ``environment`` will be merged on top of the service's. From 273330efc6d71a996f5c697498ec2c458a7e5fe2 Mon Sep 17 00:00:00 2001 From: Ben Hoyt Date: Tue, 11 Jul 2023 21:27:45 +1200 Subject: [PATCH 5/8] Rename "context" to "service_context" --- ops/model.py | 8 ++++---- ops/pebble.py | 14 +++++++------- test/pebble_cli.py | 2 +- test/test_model.py | 4 ++-- test/test_pebble.py | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ops/model.py b/ops/model.py index 249c5bca7..47cc2ea67 100644 --- a/ops/model.py +++ b/ops/model.py @@ -2366,7 +2366,7 @@ def exec( # noqa self, command: List[str], *, - context: Optional[str] = None, + service_context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2388,7 +2388,7 @@ def exec( # noqa self, command: List[str], *, - context: Optional[str] = None, + service_context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2408,7 +2408,7 @@ def exec( self, command: List[str], *, - context: Optional[str] = None, + service_context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2429,7 +2429,7 @@ def exec( """ return self._pebble.exec( command, - context=context, + service_context=service_context, environment=environment, working_dir=working_dir, timeout=timeout, diff --git a/ops/pebble.py b/ops/pebble.py index 2fbf7c729..5c3c7bcbd 100644 --- a/ops/pebble.py +++ b/ops/pebble.py @@ -99,7 +99,7 @@ total=False) ExecDict = typing.TypedDict('ExecDict', {'command': str, - 'context': str, + 'service-context': str, 'environment': Dict[str, str], 'user-id': Optional[int], 'user': str, @@ -2154,7 +2154,7 @@ def exec( # noqa self, command: List[str], *, - context: Optional[str] = None, + service_context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2176,7 +2176,7 @@ def exec( # noqa self, command: List[str], *, - context: Optional[str] = None, + service_context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2196,7 +2196,7 @@ def exec( self, command: List[str], *, - context: Optional[str] = None, + service_context: Optional[str] = None, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, @@ -2281,8 +2281,8 @@ def exec( Args: command: Command to execute: the first item is the name (or path) of the executable, the rest of the items are the arguments. - context: If specified, run the command in the context of this - service. Specifically, inherit its environment variables, + service_context: If specified, run the command in the context of + this service. Specifically, inherit its environment variables, user/group settings, and working directory. The other exec options will override the service context; ``environment`` will be merged on top of the service's. @@ -2351,7 +2351,7 @@ def exec( body = { 'command': command, - 'context': context, + 'service-context': service_context, 'environment': environment or {}, 'working-dir': working_dir, 'timeout': _format_timeout(timeout) if timeout is not None else None, diff --git a/test/pebble_cli.py b/test/pebble_cli.py index a5e13dd1c..adeebdea7 100644 --- a/test/pebble_cli.py +++ b/test/pebble_cli.py @@ -197,7 +197,7 @@ def main(): process = client.exec( args.exec_command, - context=args.context, + service_context=args.context, environment=environment, working_dir=args.working_dir, timeout=args.timeout, diff --git a/test/test_model.py b/test/test_model.py index 87bfeb268..be09fa5e4 100755 --- a/test/test_model.py +++ b/test/test_model.py @@ -1781,7 +1781,7 @@ def test_exec(self): self.pebble.responses.append('fake_exec_process') p = self.container.exec( ['echo', 'foo'], - context='srv1', + service_context='srv1', environment={'K1': 'V1', 'K2': 'V2'}, working_dir='WD', timeout=10.5, @@ -1797,7 +1797,7 @@ def test_exec(self): ) self.assertEqual(self.pebble.requests, [ ('exec', ['echo', 'foo'], dict( - context='srv1', + service_context='srv1', environment={'K1': 'V1', 'K2': 'V2'}, working_dir='WD', timeout=10.5, diff --git a/test/test_pebble.py b/test/test_pebble.py index e5ba3ec0e..76d205d92 100644 --- a/test/test_pebble.py +++ b/test/test_pebble.py @@ -2642,11 +2642,11 @@ def add_responses(self, change_id, exit_code, change_err=None): return (stdio, stderr, control) def build_exec_data( - self, command, context=None, environment=None, working_dir=None, timeout=None, + self, command, service_context=None, environment=None, working_dir=None, timeout=None, user_id=None, user=None, group_id=None, group=None, combine_stderr=False): return { 'command': command, - 'context': context, + 'service-context': service_context, 'environment': environment or {}, 'working-dir': working_dir, 'timeout': f'{timeout:.3f}s' if timeout is not None else None, From 15831c2a5daf422e2d3cc3877fc66fae68252b3e Mon Sep 17 00:00:00 2001 From: Ben Hoyt Date: Wed, 12 Jul 2023 14:59:12 +1200 Subject: [PATCH 6/8] Error if exec used with service_context on Juju that doesn't support it It will be supported on Juju 3.1.6+ and 3.2.1+ and 3.3+ --- ops/jujuversion.py | 15 +++++++++++++++ ops/model.py | 5 +++++ ops/pebble.py | 1 + test/test_jujuversion.py | 11 +++++++++++ test/test_model.py | 7 +++++++ 5 files changed, 39 insertions(+) diff --git a/ops/jujuversion.py b/ops/jujuversion.py index bb8151b13..9477c3a31 100755 --- a/ops/jujuversion.py +++ b/ops/jujuversion.py @@ -128,3 +128,18 @@ def supports_open_port_on_k8s(self) -> bool: """Report whether this Juju version supports open-port on Kubernetes.""" # Support added: https://bugs.launchpad.net/juju/+bug/1920960 return (self.major, self.minor, self.patch) >= (3, 0, 3) + + @property + def supports_exec_service_context(self) -> bool: + """Report whether this Juju version supports exec's service_context option.""" + if self.major < 3: + return False # 2.9 doesn't support it + if self.major > 3: + return True # 4.x presumably will + if self.minor == 0: + return False # 3.0 doesn't support it + if self.minor == 1: + return self.patch >= 6 # 3.1.6+ supports it + if self.minor == 2: + return self.patch >= 1 # 3.2.1+ supports it + return True # 3.3+ will diff --git a/ops/model.py b/ops/model.py index 4bd4342f3..24f8568d3 100644 --- a/ops/model.py +++ b/ops/model.py @@ -2421,6 +2421,11 @@ def exec( See :meth:`ops.pebble.Client.exec` for documentation of the parameters and return value, as well as examples. """ + if service_context is not None: + version = JujuVersion.from_environ() + if not version.supports_exec_service_context: + raise RuntimeError( + f'exec with service_context not supported on Juju version {version}') return self._pebble.exec( command, service_context=service_context, diff --git a/ops/pebble.py b/ops/pebble.py index 5c3c7bcbd..d1f9d275a 100644 --- a/ops/pebble.py +++ b/ops/pebble.py @@ -99,6 +99,7 @@ total=False) ExecDict = typing.TypedDict('ExecDict', {'command': str, + # see JujuVersion.supports_exec_service_context 'service-context': str, 'environment': Dict[str, str], 'user-id': Optional[int], diff --git a/test/test_jujuversion.py b/test/test_jujuversion.py index 7e0423058..05c2ccaab 100755 --- a/test/test_jujuversion.py +++ b/test/test_jujuversion.py @@ -80,6 +80,17 @@ def test_supports_open_port_on_k8s(self): self.assertFalse(ops.JujuVersion('3.0.2').supports_open_port_on_k8s) self.assertFalse(ops.JujuVersion('2.9.30').supports_open_port_on_k8s) + def test_supports_exec_service_context(self): + self.assertFalse(ops.JujuVersion('2.9.30').supports_exec_service_context) + self.assertTrue(ops.JujuVersion('4.0.0').supports_exec_service_context) + self.assertFalse(ops.JujuVersion('3.0.0').supports_exec_service_context) + self.assertFalse(ops.JujuVersion('3.1.5').supports_exec_service_context) + self.assertTrue(ops.JujuVersion('3.1.6').supports_exec_service_context) + self.assertFalse(ops.JujuVersion('3.2.0').supports_exec_service_context) + self.assertTrue(ops.JujuVersion('3.2.1').supports_exec_service_context) + self.assertTrue(ops.JujuVersion('3.3.0').supports_exec_service_context) + self.assertTrue(ops.JujuVersion('3.4.0').supports_exec_service_context) + def test_parsing_errors(self): invalid_versions = [ "xyz", diff --git a/test/test_model.py b/test/test_model.py index b1e063b71..d9e86c9e7 100755 --- a/test/test_model.py +++ b/test/test_model.py @@ -24,6 +24,7 @@ from collections import OrderedDict from test.test_helpers import fake_script, fake_script_calls from textwrap import dedent +from unittest.mock import patch import pytest @@ -1777,6 +1778,7 @@ def raise_error(): self.assertEqual(len(cm.output), 1) self.assertRegex(cm.output[0], r'WARNING:ops.model:.*: api error!') + @patch('model.JujuVersion.from_environ', new=lambda: ops.model.JujuVersion('3.1.6')) def test_exec(self): self.pebble.responses.append('fake_exec_process') p = self.container.exec( @@ -1814,6 +1816,11 @@ def test_exec(self): ]) self.assertEqual(p, 'fake_exec_process') + @patch('model.JujuVersion.from_environ', new=lambda: ops.model.JujuVersion('3.1.5')) + def test_exec_service_context_not_supported(self): + with self.assertRaises(RuntimeError): + self.container.exec(['foo'], service_context='srv1') + def test_send_signal(self): with self.assertRaises(TypeError): self.container.send_signal('SIGHUP') From d024ce823a42ac2a1172db1f5a7e640c3e662882 Mon Sep 17 00:00:00 2001 From: Ben Hoyt Date: Wed, 12 Jul 2023 16:27:31 +1200 Subject: [PATCH 7/8] Oh wait, 3.2.1 is already out, it'll be 3.2.2 --- ops/jujuversion.py | 2 +- test/test_jujuversion.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ops/jujuversion.py b/ops/jujuversion.py index 9477c3a31..4965df772 100755 --- a/ops/jujuversion.py +++ b/ops/jujuversion.py @@ -141,5 +141,5 @@ def supports_exec_service_context(self) -> bool: if self.minor == 1: return self.patch >= 6 # 3.1.6+ supports it if self.minor == 2: - return self.patch >= 1 # 3.2.1+ supports it + return self.patch >= 2 # 3.2.2+ supports it return True # 3.3+ will diff --git a/test/test_jujuversion.py b/test/test_jujuversion.py index 05c2ccaab..658862ae7 100755 --- a/test/test_jujuversion.py +++ b/test/test_jujuversion.py @@ -86,8 +86,8 @@ def test_supports_exec_service_context(self): self.assertFalse(ops.JujuVersion('3.0.0').supports_exec_service_context) self.assertFalse(ops.JujuVersion('3.1.5').supports_exec_service_context) self.assertTrue(ops.JujuVersion('3.1.6').supports_exec_service_context) - self.assertFalse(ops.JujuVersion('3.2.0').supports_exec_service_context) - self.assertTrue(ops.JujuVersion('3.2.1').supports_exec_service_context) + self.assertFalse(ops.JujuVersion('3.2.1').supports_exec_service_context) + self.assertTrue(ops.JujuVersion('3.2.2').supports_exec_service_context) self.assertTrue(ops.JujuVersion('3.3.0').supports_exec_service_context) self.assertTrue(ops.JujuVersion('3.4.0').supports_exec_service_context) From 001b16a87f8484bceabf5077a381fc9c461fa921 Mon Sep 17 00:00:00 2001 From: Ben Hoyt Date: Mon, 17 Jul 2023 13:55:28 +1200 Subject: [PATCH 8/8] Simplify per John's feedback --- ops/jujuversion.py | 19 ++++++++----------- test/test_jujuversion.py | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/ops/jujuversion.py b/ops/jujuversion.py index 4965df772..57153e332 100755 --- a/ops/jujuversion.py +++ b/ops/jujuversion.py @@ -132,14 +132,11 @@ def supports_open_port_on_k8s(self) -> bool: @property def supports_exec_service_context(self) -> bool: """Report whether this Juju version supports exec's service_context option.""" - if self.major < 3: - return False # 2.9 doesn't support it - if self.major > 3: - return True # 4.x presumably will - if self.minor == 0: - return False # 3.0 doesn't support it - if self.minor == 1: - return self.patch >= 6 # 3.1.6+ supports it - if self.minor == 2: - return self.patch >= 2 # 3.2.2+ supports it - return True # 3.3+ will + if (self.major, self.minor, self.patch) < (3, 1, 6): + # First released in 3.1.6 + return False + if (self.major, self.minor, self.patch) == (3, 2, 0): + # 3.2.0 was released before Pebble was updated, but all other 3.2 + # releases have the change (3.2.1 tag was never released). + return False + return True diff --git a/test/test_jujuversion.py b/test/test_jujuversion.py index 658862ae7..a22cb050d 100755 --- a/test/test_jujuversion.py +++ b/test/test_jujuversion.py @@ -86,7 +86,7 @@ def test_supports_exec_service_context(self): self.assertFalse(ops.JujuVersion('3.0.0').supports_exec_service_context) self.assertFalse(ops.JujuVersion('3.1.5').supports_exec_service_context) self.assertTrue(ops.JujuVersion('3.1.6').supports_exec_service_context) - self.assertFalse(ops.JujuVersion('3.2.1').supports_exec_service_context) + self.assertFalse(ops.JujuVersion('3.2.0').supports_exec_service_context) self.assertTrue(ops.JujuVersion('3.2.2').supports_exec_service_context) self.assertTrue(ops.JujuVersion('3.3.0').supports_exec_service_context) self.assertTrue(ops.JujuVersion('3.4.0').supports_exec_service_context)