From 557295628ee31f01bf7f0e2ad597f1e79028aa29 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Tue, 23 Jun 2026 18:55:19 +0300 Subject: [PATCH 01/18] [BREAKING] Bump Python to 3.12+ --- setup.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 86c43fff..a917adf8 100644 --- a/setup.py +++ b/setup.py @@ -11,10 +11,7 @@ import os import sys -try: - from setuptools import setup, find_packages -except ImportError: - from distutils.core import setup, find_packages +from setuptools import setup, find_packages with open('README.md') as f: _readme = f.read() @@ -28,7 +25,7 @@ long_description=_readme + '\n\n', long_description_content_type='text/markdown', url='https://github.com/adobe/ops-cli', - python_requires='>=3.5', + python_requires='>=3.12', author='Adobe', author_email='noreply@adobe.com', license='Apache2', @@ -39,9 +36,7 @@ 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', From 423568e3741d14ad6303e3bdca1c8da22de37a1f Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Tue, 23 Jun 2026 15:46:43 +0300 Subject: [PATCH 02/18] [CLEAN-UP] Remove six package --- requirements.txt | 1 - src/ops/__init__.py | 4 +--- src/ops/ansible/filter_plugins/commonfilters.py | 4 +--- src/ops/cli/parser.py | 9 +-------- src/ops/inventory/azurerm.py | 14 ++++++-------- src/ops/inventory/caching.py | 6 +----- src/ops/inventory/plugin/azr.py | 7 ++----- src/ops/simpleconsul.py | 6 ++---- src/ops/simplevault.py | 6 +++--- tests/e2e/test_inventory.py | 7 ------- tests/e2e/test_playbook.py | 7 ------- tests/e2e/test_ssh.py | 10 ---------- 12 files changed, 17 insertions(+), 64 deletions(-) diff --git a/requirements.txt b/requirements.txt index b3298966..56161d99 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,5 +15,4 @@ passgen inflection==0.5.1 kubernetes==33.1.0 himl==0.18.0 -six GitPython==3.1.* diff --git a/src/ops/__init__.py b/src/ops/__init__.py index 45672d9d..9dba1d1b 100644 --- a/src/ops/__init__.py +++ b/src/ops/__init__.py @@ -13,8 +13,6 @@ from distutils.version import StrictVersion from subprocess import call, Popen, PIPE -from six import PY3 - from .cli import display @@ -34,7 +32,7 @@ def __call__(self, result, pass_trough=True, cwd=None): try: return self._execute(result, pass_trough, cwd) except Exception as ex: - display(str(ex) if PY3 else ex.message, stderr=True, color='red') + display(str(ex), stderr=True, color='red') display( '------- TRACEBACK ----------', stderr=True, diff --git a/src/ops/ansible/filter_plugins/commonfilters.py b/src/ops/ansible/filter_plugins/commonfilters.py index 78bb93aa..40dc7e21 100644 --- a/src/ops/ansible/filter_plugins/commonfilters.py +++ b/src/ops/ansible/filter_plugins/commonfilters.py @@ -11,8 +11,6 @@ from __future__ import absolute_import import os from ops.cli import display -from six import iteritems - def read_file(fname): if os.path.exists(fname): @@ -100,7 +98,7 @@ def write_vault( namespace=None, mount_point=None, auto_prompt=auto_prompt) new_data = {} if isinstance(data, dict): - for k,v in iteritems(data): + for k, v in data.items(): new_data[k] = str(v) elif key: new_data[key] = str(data) diff --git a/src/ops/cli/parser.py b/src/ops/cli/parser.py index 8c1356ea..02cd9a8f 100644 --- a/src/ops/cli/parser.py +++ b/src/ops/cli/parser.py @@ -12,8 +12,6 @@ import sys -from six import PY3 - class RootParser(object): def __init__(self, sub_parsers=None): @@ -59,12 +57,7 @@ def _check_args_for_unicode(args): try: for value in args: - if not PY3 and isinstance(value, unicode): - # Python3 or some Python3 compatibility mode can make - # arguments to be unicode, not str - value.encode('utf-8').encode('utf-8') - # Python 2 str, check if it can be represented in utf8 - elif isinstance(value, str): + if isinstance(value, str): value.encode('utf-8') except UnicodeDecodeError as e: print('Invalid character in argument "{0}", most likely an "en dash", replace it with normal dash -'.format( diff --git a/src/ops/inventory/azurerm.py b/src/ops/inventory/azurerm.py index 3247ee95..db09672b 100644 --- a/src/ops/inventory/azurerm.py +++ b/src/ops/inventory/azurerm.py @@ -195,7 +195,7 @@ ''' import argparse -from six.moves import configparser +import configparser import json import os import re @@ -205,8 +205,6 @@ from os.path import expanduser -from six import iteritems - HAS_AZURE = True HAS_AZURE_EXC = None @@ -323,7 +321,7 @@ def _get_profile(self, profile="default"): def _get_env_credentials(self): env_credentials = dict() - for attribute, env_variable in iteritems(AZURE_CREDENTIAL_ENV_MAPPING): + for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): env_credentials[attribute] = os.environ.get(env_variable, None) if env_credentials['profile'] is not None: @@ -343,7 +341,7 @@ def _get_credentials(self, params): self.log('Getting credentials') arg_credentials = dict() - for attribute, env_variable in iteritems(AZURE_CREDENTIAL_ENV_MAPPING): + for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): arg_credentials[attribute] = getattr(params, attribute) # try module params @@ -694,7 +692,7 @@ def _add_host(self, vars): self._inventory['azure'].append(host_name) if self.group_by_tag and vars.get('tags'): - for key, value in iteritems(vars['tags']): + for key, value in vars['tags'].items(): safe_key = self._to_safe(key) safe_value = self._to_safe(value) if not self._inventory.get(safe_key): @@ -756,7 +754,7 @@ def _to_boolean(self, value): def _get_env_settings(self): env_settings = dict() - for attribute, env_variable in iteritems(AZURE_CONFIG_SETTINGS): + for attribute, env_variable in AZURE_CONFIG_SETTINGS.items(): env_settings[attribute] = os.environ.get(env_variable, None) return env_settings @@ -773,7 +771,7 @@ def _load_settings(self): config = None settings = None try: - config = ConfigParser.ConfigParser() + config = configparser.ConfigParser() config.read(path) except BaseException: pass diff --git a/src/ops/inventory/caching.py b/src/ops/inventory/caching.py index 1c37cc07..03cc8cc3 100644 --- a/src/ops/inventory/caching.py +++ b/src/ops/inventory/caching.py @@ -13,8 +13,6 @@ import os import time -from six import PY3 - def cache_callback_result(directory, func, max_age, cache_key_args): directory = os.path.expanduser(directory) @@ -27,9 +25,7 @@ def cache_callback_result(directory, func, max_age, cache_key_args): def get_cache_path(dir, args): m = hashlib.md5() - json_dump = json.dumps(args) - if PY3: - json_dump = json_dump.encode('utf-8') + json_dump = json.dumps(args).encode('utf-8') m.update(json_dump) return os.path.join(dir, m.hexdigest()) diff --git a/src/ops/inventory/plugin/azr.py b/src/ops/inventory/plugin/azr.py index 0eb79098..3209b1fa 100644 --- a/src/ops/inventory/plugin/azr.py +++ b/src/ops/inventory/plugin/azr.py @@ -11,8 +11,6 @@ from ops.inventory.azurerm import * from ansible.playbook.play import display -from six import iteritems - class DictGlue(object): def __init__(self, data={}): @@ -96,7 +94,7 @@ def __init__(self, args={}): self.get_inventory() bastions = {} - for host, hostvars in iteritems(self._inventory['_meta']['hostvars']): + for host, hostvars in self._inventory['_meta']['hostvars'].items(): if ('role' in hostvars['tags'] and hostvars['tags']['role'] == 'bastion') or \ (self._args.bastion_tag in hostvars['tags'] and hostvars['tags'][self._args.bastion_tag] == 'bastion'): @@ -111,8 +109,7 @@ def __init__(self, args={}): color='yellow') if bastions: - for host, hostvars in iteritems( - self._inventory['_meta']['hostvars']): + for host, hostvars in self._inventory['_meta']['hostvars'].items(): if ('role' in hostvars['tags'] and hostvars['tags']['role'] == 'bastion') or \ (self._args.bastion_tag in hostvars['tags'] and diff --git a/src/ops/simpleconsul.py b/src/ops/simpleconsul.py index d5000350..193ef8d8 100644 --- a/src/ops/simpleconsul.py +++ b/src/ops/simpleconsul.py @@ -18,8 +18,6 @@ import consul import hashmerge -from six import iteritems - DEFAULT_CONNECT = { 'host': '127.0.0.1', 'port': 8500, @@ -96,7 +94,7 @@ def get(self, key, recurse=False): index, keys_list = self.conn.kv.get(key + '/', recurse=recurse) if keys_list: keys_dict = {i['Key']: i['Value'] for i in keys_list} - for k, v in iteritems(keys_dict): + for k, v in keys_dict.items(): tmp = {} path_atoms = k.split('/') leaf = path_atoms.pop() @@ -119,5 +117,5 @@ def put(self, key, value): for item in value: self.conn.kv.put(key, item, "True") elif isinstance(value, dict): - for k, v in iteritems(value): + for k, v in value.items(): self.put(key + '/' + k, v) diff --git a/src/ops/simplevault.py b/src/ops/simplevault.py index a805ea83..8ff9f81c 100644 --- a/src/ops/simplevault.py +++ b/src/ops/simplevault.py @@ -24,7 +24,7 @@ import hvac import getpass from .cli import display -from six import iteritems, string_types + MAX_LDAP_ATTEMPTS = 3 @@ -219,10 +219,10 @@ def check(self, path, key): def put(self, path, value, lease=None, wrap_ttl=None): payload = {} - if isinstance(value, (string_types, int, float, bool)): + if isinstance(value, (str, int, float, bool)): payload['value'] = str(value) elif isinstance(value, dict): - for k, v in iteritems(value): + for k, v in value.items(): payload[k] = str(v) else: raise Exception('Unsupported data type for secret payload') diff --git a/tests/e2e/test_inventory.py b/tests/e2e/test_inventory.py index 40e25d54..e00fe044 100644 --- a/tests/e2e/test_inventory.py +++ b/tests/e2e/test_inventory.py @@ -12,7 +12,6 @@ # coding=utf-8 import os import pytest -from six import PY3 from ops.main import AppContainer from simpledi import * @@ -69,9 +68,3 @@ def test_inventory_limit(capsys): print(err) assert 'bastion.host' in out assert 'web1.host' not in out - - -if not PY3: - def test_inventory_limit_unicode_dash(): - with pytest.raises(UnicodeDecodeError): - run(current_dir + '/fixture/inventory/clusters/plugin_generator.yaml', 'inventory', '––limit', 'bastion') diff --git a/tests/e2e/test_playbook.py b/tests/e2e/test_playbook.py index 94014809..f36d76f1 100644 --- a/tests/e2e/test_playbook.py +++ b/tests/e2e/test_playbook.py @@ -13,7 +13,6 @@ import pytest from ops import display -from six import PY3 from ops.main import AppContainer from simpledi import * @@ -54,9 +53,3 @@ def test_loading_of_modules_and_extensions(capsys, app): # cluster is present as a variable in the command line assert '-e cluster=test' in command['command'] -if not PY3: - def test_ssh_user_unicode_dash(capsys, app): - with pytest.raises(UnicodeDecodeError): - root_dir = current_dir + '/fixture/ansible' - app([u'–vv', '--root-dir', root_dir, 'clusters/test.yaml', 'play', - 'playbooks/play_module.yaml']).run() diff --git a/tests/e2e/test_ssh.py b/tests/e2e/test_ssh.py index 8de4a189..316dd039 100644 --- a/tests/e2e/test_ssh.py +++ b/tests/e2e/test_ssh.py @@ -12,7 +12,6 @@ import os import re -from six import PY3 import test_inventory import pytest @@ -102,15 +101,6 @@ def test_ssh_scb_user_noscb(): assert "scb.example.com" not in command['command'] - - -if not PY3: - def test_ssh_user_unicode_dash(): - with pytest.raises(UnicodeDecodeError): - run(current_dir + '/fixture/inventory/clusters/plugin_generator.yaml', 'ssh', - 'bastion', '–l', 'remote_user') - - def test_ssh_user_default(): # we take the default system user command = run(current_dir + '/fixture/inventory/clusters/plugin_generator.yaml', 'ssh', From cfcb6fe73b6599e80b481d4eed7d4cc126b852a5 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:10:11 +0300 Subject: [PATCH 03/18] [CLEAN-UP] Remove pkg_resources references --- src/ops/__init__.py | 5 ++--- src/ops/cli/terraform.py | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ops/__init__.py b/src/ops/__init__.py index 9dba1d1b..9ea0a986 100644 --- a/src/ops/__init__.py +++ b/src/ops/__init__.py @@ -8,8 +8,8 @@ # OF ANY KIND, either express or implied. See the License for the specific language # governing permissions and limitations under the License. -import pkg_resources import re +from importlib.metadata import version as importlib_version from distutils.version import StrictVersion from subprocess import call, Popen, PIPE @@ -17,8 +17,7 @@ def validate_ops_version(min_ops_version): - current_ops_version = [ - x.version for x in pkg_resources.working_set if x.project_name == "ops-cli"][0] + current_ops_version = importlib_version('ops-cli') if StrictVersion(current_ops_version) < StrictVersion(min_ops_version): raise Exception("The current ops version {0} is lower than the minimum required version {1}. " "Please upgrade by following the instructions seen here: " diff --git a/src/ops/cli/terraform.py b/src/ops/cli/terraform.py index 9d5e5c75..4c241163 100644 --- a/src/ops/cli/terraform.py +++ b/src/ops/cli/terraform.py @@ -17,7 +17,6 @@ from ops.hierarchical.composition_config_generator import TerraformConfigGenerator from distutils.version import StrictVersion from ops import validate_ops_version -import pkg_resources logger = logging.getLogger(__name__) From 09d3be076c6fc0d413c54da18e0986774815a4be Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:05:55 +0300 Subject: [PATCH 04/18] [CLEAN-UP] Replace distutils with stdlib (shutil) and packaging equivalents --- requirements.txt | 1 + src/ops/__init__.py | 4 ++-- src/ops/cli/terraform.py | 1 - src/ops/inventory/azurerm.py | 4 ++-- src/ops/inventory/generator.py | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 56161d99..cbc63c20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ python-consul hvac==1.2.1 passgen inflection==0.5.1 +packaging==26.2 kubernetes==33.1.0 himl==0.18.0 GitPython==3.1.* diff --git a/src/ops/__init__.py b/src/ops/__init__.py index 9ea0a986..4af212ea 100644 --- a/src/ops/__init__.py +++ b/src/ops/__init__.py @@ -10,7 +10,7 @@ import re from importlib.metadata import version as importlib_version -from distutils.version import StrictVersion +from packaging.version import Version from subprocess import call, Popen, PIPE from .cli import display @@ -18,7 +18,7 @@ def validate_ops_version(min_ops_version): current_ops_version = importlib_version('ops-cli') - if StrictVersion(current_ops_version) < StrictVersion(min_ops_version): + if Version(current_ops_version) < Version(min_ops_version): raise Exception("The current ops version {0} is lower than the minimum required version {1}. " "Please upgrade by following the instructions seen here: " "https://github.com/adobe/ops-cli#installing".format(current_ops_version, min_ops_version)) diff --git a/src/ops/cli/terraform.py b/src/ops/cli/terraform.py index 4c241163..dfaa3a66 100644 --- a/src/ops/cli/terraform.py +++ b/src/ops/cli/terraform.py @@ -15,7 +15,6 @@ from ops.cli.parser import SubParserConfig from ops.terraform.terraform_cmd_generator import TerraformCommandGenerator from ops.hierarchical.composition_config_generator import TerraformConfigGenerator -from distutils.version import StrictVersion from ops import validate_ops_version logger = logging.getLogger(__name__) diff --git a/src/ops/inventory/azurerm.py b/src/ops/inventory/azurerm.py index db09672b..4f1fad49 100644 --- a/src/ops/inventory/azurerm.py +++ b/src/ops/inventory/azurerm.py @@ -201,7 +201,7 @@ import re import sys -from distutils.version import LooseVersion +from packaging.version import Version from os.path import expanduser @@ -826,7 +826,7 @@ def main(): "The Azure python sdk is not installed " "(try 'pip install azure==2.0.0rc5') - {0}".format(HAS_AZURE_EXC)) - if LooseVersion(azure_compute_version) != LooseVersion(AZURE_MIN_VERSION): + if Version(azure_compute_version) != Version(AZURE_MIN_VERSION): sys.exit("Expecting azure.mgmt.compute.__version__ to be {0}. Found version {1} " "Do you have Azure == 2.0.0rc5 installed?".format(AZURE_MIN_VERSION, azure_compute_version)) diff --git a/src/ops/inventory/generator.py b/src/ops/inventory/generator.py index c05d709f..39ef1069 100644 --- a/src/ops/inventory/generator.py +++ b/src/ops/inventory/generator.py @@ -11,7 +11,7 @@ import os import tempfile import uuid -from distutils.dir_util import copy_tree +import shutil import ansible.inventory as ansible_inventory import ansible.vars as ansible_vars @@ -198,7 +198,7 @@ def supports(self, config): return config.get('directory') is not None def generate(self, dest, config): - copy_tree(self.root_dir + '/' + config['directory'], dest) + shutil.copytree(self.root_dir + '/' + config['directory'], dest, dirs_exist_ok=True) class PluginInventoryGenerator(object): From 5bf4efec165105265e547ebcd216e8d4e1f2de0c Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Fri, 26 Jun 2026 13:13:32 +0300 Subject: [PATCH 05/18] [DEPS] Upgrade ansible from 8.7.0 to 11.13.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cbc63c20..4c6449f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ awscli==1.42.30 boto3==1.40.30 botocore==1.40.30 urllib3==2.6.0 -ansible==8.7.0 +ansible==11.13.0 azure-common==1.1.28 azure==4.0.0 msrestazure==0.6.4 From 71223f4813d8605f483dbcb0b0d1f765846da415 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Fri, 26 Jun 2026 18:56:35 +0300 Subject: [PATCH 06/18] [DEPS] Replace azure/msrestazure with azure-* modern packages Misc: - removed dead code in azurerm.py --- requirements.txt | 7 +++-- src/ops/inventory/azurerm.py | 58 +++++++++++++----------------------- 2 files changed, 24 insertions(+), 41 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4c6449f1..ab11b832 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,10 @@ boto3==1.40.30 botocore==1.40.30 urllib3==2.6.0 ansible==11.13.0 -azure-common==1.1.28 -azure==4.0.0 -msrestazure==0.6.4 +azure-identity==1.25.3 +azure-mgmt-compute==38.1.0 +azure-mgmt-network==30.2.0 +azure-mgmt-resource==26.0.0 Jinja2==3.1.6 hashmerge python-consul diff --git a/src/ops/inventory/azurerm.py b/src/ops/inventory/azurerm.py index 4f1fad49..50a79c53 100644 --- a/src/ops/inventory/azurerm.py +++ b/src/ops/inventory/azurerm.py @@ -201,21 +201,16 @@ import re import sys -from packaging.version import Version - from os.path import expanduser HAS_AZURE = True HAS_AZURE_EXC = None try: - from msrestazure.azure_exceptions import CloudError - from azure.mgmt.compute import __version__ as azure_compute_version - from azure.common import AzureMissingResourceHttpError, AzureHttpError - from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials - from azure.mgmt.network.network_management_client import NetworkManagementClient - from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient - from azure.mgmt.compute.compute_management_client import ComputeManagementClient + from azure.identity import ClientSecretCredential, UsernamePasswordCredential + from azure.mgmt.network import NetworkManagementClient + from azure.mgmt.resource.resources import ResourceManagementClient + from azure.mgmt.compute import ComputeManagementClient except ImportError as exc: HAS_AZURE_EXC = exc HAS_AZURE = False @@ -242,9 +237,6 @@ group_by_tag='AZURE_GROUP_BY_TAG' ) -AZURE_MIN_VERSION = "0.30.0rc5" - - def azure_id_to_dict(id): pieces = re.sub(r'^\/', '', id).split('/') result = {} @@ -277,18 +269,25 @@ def __init__(self, args): self.log("setting subscription_id") self.subscription_id = self.credentials['subscription_id'] - if self.credentials.get('client_id') is not None and \ - self.credentials.get('secret') is not None and \ - self.credentials.get('tenant') is not None: - self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'], - secret=self.credentials['secret'], - tenant=self.credentials['tenant']) + if self.credentials.get('client_id') is None or self.credentials.get('tenant') is None: + self.fail("Failed to authenticate with provided credentials. " + "client_id and tenant are required for all authentication methods.") + + if self.credentials.get('secret') is not None: + self.azure_credentials = ClientSecretCredential( + tenant_id=self.credentials['tenant'], + client_id=self.credentials['client_id'], + client_secret=self.credentials['secret']) elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None: - self.azure_credentials = UserPassCredentials( - self.credentials['ad_user'], self.credentials['password']) + self.azure_credentials = UsernamePasswordCredential( + client_id=self.credentials['client_id'], + username=self.credentials['ad_user'], + password=self.credentials['password'], + tenant_id=self.credentials['tenant']) else: self.fail("Failed to authenticate with provided credentials. Some attributes were missing. " - "Credentials must include client_id, secret and tenant or ad_user and password.") + "Credentials must include client_id, tenant and secret (service principal) " + "or client_id, tenant, ad_user and password (user/password).") def log(self, msg): if self.debug: @@ -818,20 +817,3 @@ def _to_safe(self, word): if not self.replace_dash_in_groups: regex += r"\-" return re.sub(regex + "]", "_", word) - - -def main(): - if not HAS_AZURE: - sys.exit( - "The Azure python sdk is not installed " - "(try 'pip install azure==2.0.0rc5') - {0}".format(HAS_AZURE_EXC)) - - if Version(azure_compute_version) != Version(AZURE_MIN_VERSION): - sys.exit("Expecting azure.mgmt.compute.__version__ to be {0}. Found version {1} " - "Do you have Azure == 2.0.0rc5 installed?".format(AZURE_MIN_VERSION, azure_compute_version)) - - AzureInventory() - - -if __name__ == '__main__': - main() From 605a760b3ea273f5f049d833f2a92731ed7997a8 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:00:30 +0300 Subject: [PATCH 07/18] [DEPS] Upgrade himl from 0.18.0 to 0.20.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab11b832..50fdac2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,5 +16,5 @@ passgen inflection==0.5.1 packaging==26.2 kubernetes==33.1.0 -himl==0.18.0 +himl==0.20.0 GitPython==3.1.* From d2e30391a8ca9891f1b86ea1356f6df9c031a1df Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:34:28 +0300 Subject: [PATCH 08/18] [DEPS] Migrate from python-consul to py-consul 1.7.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 50fdac2c..7dda9d5c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ azure-mgmt-network==30.2.0 azure-mgmt-resource==26.0.0 Jinja2==3.1.6 hashmerge -python-consul +py-consul==1.7.1 hvac==1.2.1 passgen inflection==0.5.1 From 465c5a434b7090563c86333d07ecc7f9b7eccf64 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Fri, 26 Jun 2026 19:42:41 +0300 Subject: [PATCH 09/18] [CLEAN-UP] Remove dead code from parser (_check_args_for_unicode) Note: - _check_args_for_unicode is not needed, as Python 3 already handles Unicode chars --- src/ops/cli/parser.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/ops/cli/parser.py b/src/ops/cli/parser.py index 02cd9a8f..315cdfeb 100644 --- a/src/ops/cli/parser.py +++ b/src/ops/cli/parser.py @@ -10,8 +10,6 @@ import argparse -import sys - class RootParser(object): def __init__(self, sub_parsers=None): @@ -50,26 +48,10 @@ def _get_parser(self): return parser - @staticmethod - def _check_args_for_unicode(args): - if args is None: - args = sys.argv - - try: - for value in args: - if isinstance(value, str): - value.encode('utf-8') - except UnicodeDecodeError as e: - print('Invalid character in argument "{0}", most likely an "en dash", replace it with normal dash -'.format( - e.args[1])) - raise - def parse_args(self, args=None): - RootParser._check_args_for_unicode(args) return self._get_parser().parse_args(args) def parse_known_args(self, args=None): - RootParser._check_args_for_unicode(args) return self._get_parser().parse_known_args(args) From 7fd235d1d1a8037091cb7d0220d2ae345c8ed3a1 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Fri, 26 Jun 2026 20:03:33 +0300 Subject: [PATCH 10/18] [DEPS] Upgrade urllib3 from 2.6.0 to 2.7.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7dda9d5c..f68f6ed1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ simpledi==0.4.1 awscli==1.42.30 boto3==1.40.30 botocore==1.40.30 -urllib3==2.6.0 +urllib3==2.7.0 ansible==11.13.0 azure-identity==1.25.3 azure-mgmt-compute==38.1.0 From 8f9a12e1cb2093eb94764f4a8999ff675123e233 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Sun, 28 Jun 2026 07:35:34 +0300 Subject: [PATCH 11/18] [DEPS] Pin hashmerge to 0.2 and passgen to 1.1.1 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index f68f6ed1..81c2e6b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,10 +9,10 @@ azure-mgmt-compute==38.1.0 azure-mgmt-network==30.2.0 azure-mgmt-resource==26.0.0 Jinja2==3.1.6 -hashmerge +hashmerge==0.2 py-consul==1.7.1 hvac==1.2.1 -passgen +passgen==1.1.1 inflection==0.5.1 packaging==26.2 kubernetes==33.1.0 From db16f48a6106b571612408401c01e5a3d6e51102 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Sun, 28 Jun 2026 07:42:56 +0300 Subject: [PATCH 12/18] [DEPS] Upgrade awscli from 1.42.30 to 1.45.36 and boto3/botocore from 1.40.30 to 1.43.36 --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 81c2e6b0..8aaf5fca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ simpledi==0.4.1 -awscli==1.42.30 -boto3==1.40.30 -botocore==1.40.30 +awscli==1.45.36 +boto3==1.43.36 +botocore==1.43.36 urllib3==2.7.0 ansible==11.13.0 azure-identity==1.25.3 From e21b202d3f8b454aa83737f89eab225757bd4401 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Sun, 28 Jun 2026 10:51:18 +0300 Subject: [PATCH 13/18] [DEPS] Upgrade hvac from 1.2.1 to 2.4.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8aaf5fca..819ca830 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ azure-mgmt-resource==26.0.0 Jinja2==3.1.6 hashmerge==0.2 py-consul==1.7.1 -hvac==1.2.1 +hvac==2.4.0 passgen==1.1.1 inflection==0.5.1 packaging==26.2 From acf250ab0929179c4ecaa0a9eb9c93076e1531d6 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Fri, 3 Jul 2026 15:03:04 +0300 Subject: [PATCH 14/18] [DEPS] Remove kubernetes package (unused) --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 819ca830..97cc2bf7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,5 @@ hvac==2.4.0 passgen==1.1.1 inflection==0.5.1 packaging==26.2 -kubernetes==33.1.0 himl==0.20.0 GitPython==3.1.* From 17eaed30f459e027d12c3ea1314efe63185fdfeb Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:38:39 +0300 Subject: [PATCH 15/18] [BUGFIX] Python 2 leftovers clean-up - Replace basestring with str - Drop .encode('utf-8) calls - Fix broken urllib import - Add explicit dependency on requests --- requirements.txt | 1 + .../ansible/filter_plugins/commonfilters.py | 1 - src/ops/inventory/SKMS.py | 15 ++-- src/ops/inventory/plugin/skms.py | 70 ++++++++----------- 4 files changed, 38 insertions(+), 49 deletions(-) diff --git a/requirements.txt b/requirements.txt index 97cc2bf7..f89bb77f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ azure-mgmt-resource==26.0.0 Jinja2==3.1.6 hashmerge==0.2 py-consul==1.7.1 +requests==2.34.2 hvac==2.4.0 passgen==1.1.1 inflection==0.5.1 diff --git a/src/ops/ansible/filter_plugins/commonfilters.py b/src/ops/ansible/filter_plugins/commonfilters.py index 40dc7e21..e61e98f4 100644 --- a/src/ops/ansible/filter_plugins/commonfilters.py +++ b/src/ops/ansible/filter_plugins/commonfilters.py @@ -8,7 +8,6 @@ #OF ANY KIND, either express or implied. See the License for the specific language #governing permissions and limitations under the License. -from __future__ import absolute_import import os from ops.cli import display diff --git a/src/ops/inventory/SKMS.py b/src/ops/inventory/SKMS.py index c9ab2533..0ded6d11 100644 --- a/src/ops/inventory/SKMS.py +++ b/src/ops/inventory/SKMS.py @@ -17,16 +17,15 @@ """ # This version of the class requires the following: -# - Python 2.6 +# - Python 3.12 # - Python support for Requests # - Python support for JSON -# @version v1.10, 2016-06-30 import socket import os import getpass import requests import inspect -import urllib +from urllib.parse import quote import json from os.path import expanduser @@ -34,7 +33,7 @@ class WebApiClient(object): """Class to allow easy access to the SKMS Web API""" # Version Constants - CLIENT_TYPE = "python3.7.requests" + CLIENT_TYPE = "python3.12.requests" CLIENT_VERSION = "1.10" # Properties @@ -150,7 +149,7 @@ def enable_skms_session_optimization(self, skms_session_storage_file): if ( isinstance(session_info, dict) and 'skms_csrf_token' in session_info and - isinstance(session_info['skms_csrf_token'], basestring) and + isinstance(session_info['skms_csrf_token'], str) and session_info['skms_csrf_token'].strip() != "" ): self.set_skms_csrf_token(session_info['skms_csrf_token']) @@ -166,7 +165,7 @@ def get_skms_session_id(self): def set_skms_session_id(self, skms_session_id): """Set the SKMS session id""" if ( - isinstance(skms_session_id, basestring) and + isinstance(skms_session_id, str) and skms_session_id.strip() != "" ): self.skms_session_id = skms_session_id @@ -267,7 +266,7 @@ def send_request(self, object_name, method_name, method_param_dict=None): verify=self.verify_ssl_chain, timeout=self.request_timeout, cert=self.trusted_cert_file_path, - cookies={cookie_name: urllib.quote(self.skms_session_id)} + cookies={cookie_name: quote(self.skms_session_id)} ) else: # Creating new session @@ -434,7 +433,7 @@ def get_message_list_by_type(self, message_type=''): ): for message in response_dict['messages']: if ( - isinstance(message_type, basestring) and + isinstance(message_type, str) and (message_type.strip() == "" or message['type'].lower() == message_type.lower()) ): diff --git a/src/ops/inventory/plugin/skms.py b/src/ops/inventory/plugin/skms.py index 47a07af1..fd7a1a62 100644 --- a/src/ops/inventory/plugin/skms.py +++ b/src/ops/inventory/plugin/skms.py @@ -120,76 +120,66 @@ def skms(args): # step through device services to add them as groups for device_service in info['device_service']: - device_service = device_service.replace( - device_service_strip, '').encode('utf-8') + device_service = device_service.replace(device_service_strip, '') # add device service to top-level dictionary as a group if device_service not in dictionary_of_hosts: dictionary_of_hosts[device_service] = {"hosts": []} # add host to array of that device service group if info['name'] not in dictionary_of_hosts[device_service]['hosts']: - dictionary_of_hosts[device_service]['hosts'].append( - info['name'].encode('utf-8')) + dictionary_of_hosts[device_service]['hosts'].append(info['name']) # add environment to top-level dictionary as a group if info['environment'] not in dictionary_of_hosts: - dictionary_of_hosts[info['environment'].encode( - 'utf-8')] = {"hosts": []} + dictionary_of_hosts[info['environment']] = {"hosts": []} # add host to array of that environment group if info['name'] not in dictionary_of_hosts[info['environment']]['hosts']: - dictionary_of_hosts[info['environment']]['hosts'].append( - info['name'].encode('utf-8')) + dictionary_of_hosts[info['environment']]['hosts'].append(info['name']) # add primary_ip_address to top-level dictionary as a group if info['primary_ip_address'] not in dictionary_of_hosts: - dictionary_of_hosts[info['primary_ip_address'].encode( - 'utf-8')] = {"hosts": []} + dictionary_of_hosts[info['primary_ip_address']] = {"hosts": []} if info['name'] not in dictionary_of_hosts[info['primary_ip_address']]['hosts']: - dictionary_of_hosts[info['primary_ip_address'] - ]['hosts'].append(info['name'].encode('utf-8')) + dictionary_of_hosts[info['primary_ip_address']]['hosts'].append(info['name']) # add owner to top-level dictionary as a group if info['owner'] not in dictionary_of_hosts: - dictionary_of_hosts[info['owner'].encode('utf-8')] = {"hosts": []} + dictionary_of_hosts[info['owner']] = {"hosts": []} if info['name'] not in dictionary_of_hosts[info['owner']]['hosts']: - dictionary_of_hosts[info['owner']]['hosts'].append( - info['name'].encode('utf-8')) + dictionary_of_hosts[info['owner']]['hosts'].append(info['name']) # add site to top-level dictionary as a group if info['site'] not in dictionary_of_hosts: - dictionary_of_hosts[info['site'].encode('utf-8')] = {"hosts": []} + dictionary_of_hosts[info['site']] = {"hosts": []} if info['name'] not in dictionary_of_hosts[info['site']]['hosts']: - dictionary_of_hosts[info['site']]['hosts'].append( - info['name'].encode('utf-8')) + dictionary_of_hosts[info['site']]['hosts'].append(info['name']) # add cluster to top-level dictionary as a group if info['cluster'] not in dictionary_of_hosts: - dictionary_of_hosts[info['cluster'].encode( - 'utf-8')] = {"hosts": []} + dictionary_of_hosts[info['cluster']] = {"hosts": []} if info['name'] not in dictionary_of_hosts[info['cluster']]['hosts']: - dictionary_of_hosts[info['cluster']]['hosts'].append( - info['name'].encode('utf-8')) + dictionary_of_hosts[info['cluster']]['hosts'].append(info['name']) # tie some extra information to hostname in the meta variables - dictionary_of_hosts['_meta']['hostvars'][info['name'].encode('utf-8')] = { - 'ec2_id': info['device_id'].encode('utf-8'), - 'ansible_ssh_host': info['primary_ip_address'].encode('utf-8'), - 'ansible_host': info['primary_ip_address'].encode('utf-8'), - 'computer_name': info['computer_name'].encode('utf-8'), - 'location': info['location_name'].encode('utf-8'), - 'name': info['name'].split('.')[0].encode('utf-8'), - 'operating_system': info['operating_system'].encode('utf-8'), - 'private_ip': info['primary_ip_address'].encode('utf-8'), + dictionary_of_hosts['_meta']['hostvars'][info['name']] = { + 'ec2_id': info['device_id'], + 'ansible_ssh_host': info['primary_ip_address'], + 'ansible_host': info['primary_ip_address'], + 'computer_name': info['computer_name'], + 'location': info['location_name'], + 'name': info['name'].split('.')[0], + 'operating_system': info['operating_system'], + 'private_ip': info['primary_ip_address'], 'tags': { - 'Adobe:Environment': info['environment'].encode('utf-8'), - 'Adobe:Owner': info['owner'].encode('utf-8'), - 'CMDB_device_service': device_service.encode('utf-8'), - 'CMDB_environment': info['environment'].encode('utf-8'), - 'CMDB_hostname': info['name'].split('.')[0].encode('utf-8'), - 'cluster': info['cluster'].encode('utf-8'), - 'environment': info['environment'].encode('utf-8'), - 'role': device_service.encode('utf-8'), - 'site': info['site'].encode('utf-8'), + 'Adobe:Environment': info['environment'], + 'Adobe:Owner': info['owner'], + 'CMDB_device_service': device_service, + 'CMDB_environment': info['environment'], + 'CMDB_hostname': info['name'].split('.')[0], + 'cluster': info['cluster'], + 'environment': info['environment'], + 'role': device_service, + 'site': info['site'], } } From 34db8347ef271aae8e76d91422c81e8e5adbdc87 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:36:41 +0300 Subject: [PATCH 16/18] [DEPS] Update Dockerfile dependencies --- Dockerfile | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 423c57b4..81645270 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ FROM python:3.12.3-alpine3.18 AS compile-image ARG TARGETARCH ARG TARGETPLATFORM -ARG TERRAFORM_VERSION="0.12.31" -ARG AZURE_CLI_VERSION="2.0.67" +ARG TERRAFORM_VERSION="0.15.5" +ARG AZURE_CLI_VERSION="2.87.0" ENV BOTO_CONFIG=/dev/null COPY . /sources/ @@ -40,10 +40,9 @@ RUN apk del --purge build FROM python:3.12.3-alpine3.18 ARG TARGETARCH ARG TARGETPLATFORM -ARG TERRAFORM_VERSION="0.12.31" -ARG VAULT_VERSION="1.1.3" -ARG KUBECTL_VERSION="v1.17.0" -ARG AWS_IAM_AUTHENTICATOR_VERSION="1.13.7/2019-06-11" +ARG TERRAFORM_VERSION="0.15.5" +ARG VAULT_VERSION="2.0.3" +ARG KUBECTL_VERSION="v1.34.1" ARG HELM_VERSION="v3.16.3" ARG HELM_FILE_VERSION="1.1.8" ARG HELM_DIFF_VERSION="2.11.0%2B5" @@ -63,7 +62,7 @@ RUN adduser ops -Du 2342 -h /home/ops \ && ops --verbose -h \ && apk del --purge build -RUN wget -q https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl -O /usr/local/bin/kubectl \ +RUN wget -q https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl -O /usr/local/bin/kubectl \ && chmod +x /usr/local/bin/kubectl RUN wget -q https://get.helm.sh/helm-${HELM_VERSION}-linux-${TARGETARCH}.tar.gz -O - | tar -xzO linux-${TARGETARCH}/helm > /usr/local/bin/helm \ @@ -76,9 +75,6 @@ RUN wget -q -O terraform.zip https://releases.hashicorp.com/terraform/${TERRAFOR RUN wget -q -O vault.zip https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_${TARGETARCH}.zip \ && unzip vault.zip -d /usr/local/bin \ && rm -rf vault.zip - -RUN wget -q https://amazon-eks.s3-us-west-2.amazonaws.com/${AWS_IAM_AUTHENTICATOR_VERSION}/bin/linux/${TARGETARCH}/aws-iam-authenticator -O /usr/local/bin/aws-iam-authenticator \ - && chmod +x /usr/local/bin/aws-iam-authenticator RUN wget -q https://github.com/helmfile/helmfile/releases/download/v${HELM_FILE_VERSION}/helmfile_${HELM_FILE_VERSION}_linux_${TARGETARCH}.tar.gz -O - | tar -xzO helmfile > /usr/local/bin/helmfile \ && chmod +x /usr/local/bin/helmfile @@ -94,7 +90,6 @@ WORKDIR /home/ops USER root RUN helm plugin install https://github.com/databus23/helm-diff --version v3.9.11 RUN helm plugin install https://github.com/jkroepke/helm-secrets --version v3.8.2 -RUN helm plugin install https://github.com/rimusz/helm-tiller # Obsolete in Helm 3 COPY --from=compile-image /azure-cli /home/ops/.local/azure-cli From f9dc2a8c62bf86acefc0e32242a19343e24eb2ac Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:50:28 +0300 Subject: [PATCH 17/18] [BUGFIX] Python 2 leftovers clean-up - fix error message retrieval - cleaned up object/super redundancy --- src/ops/__init__.py | 2 +- src/ops/ansible/filter_plugins/commonfilters.py | 6 +++--- src/ops/ansible/vars_plugins/clusterconfig.py | 4 ++-- src/ops/ansible/vars_plugins/opsconfig.py | 4 ++-- src/ops/cli/__init__.py | 2 +- src/ops/cli/config.py | 6 +++--- src/ops/cli/config_generator.py | 2 +- src/ops/cli/helmfile.py | 4 ++-- src/ops/cli/inventory.py | 2 +- src/ops/cli/packer.py | 2 +- src/ops/cli/parser.py | 4 ++-- src/ops/cli/playbook.py | 2 +- src/ops/cli/run.py | 2 +- src/ops/cli/ssh.py | 4 ++-- src/ops/cli/sync.py | 4 ++-- src/ops/cli/terraform.py | 2 +- src/ops/hierarchical/composition_config_generator.py | 8 ++++---- src/ops/inventory/SKMS.py | 2 +- src/ops/inventory/azurerm.py | 4 ++-- src/ops/inventory/ec2inventory.py | 2 +- src/ops/inventory/generator.py | 12 ++++++------ src/ops/inventory/plugin/azr.py | 2 +- src/ops/inventory/sshconfig.py | 2 +- src/ops/jinja/__init__.py | 2 +- src/ops/main.py | 2 +- src/ops/opsconfig.py | 2 +- src/ops/simpleconsul.py | 2 +- src/ops/simplessm.py | 2 +- src/ops/simplevault.py | 8 ++++---- src/ops/terraform/terraform_cmd_generator.py | 2 +- .../ansible/plugins/filter_plugins/filters.py | 2 +- 31 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/ops/__init__.py b/src/ops/__init__.py index 4af212ea..86cee00b 100644 --- a/src/ops/__init__.py +++ b/src/ops/__init__.py @@ -24,7 +24,7 @@ def validate_ops_version(min_ops_version): "https://github.com/adobe/ops-cli#installing".format(current_ops_version, min_ops_version)) -class Executor(object): +class Executor: """ All cli commands usually return a dict(command=...) that will be executed by this handler""" def __call__(self, result, pass_trough=True, cwd=None): diff --git a/src/ops/ansible/filter_plugins/commonfilters.py b/src/ops/ansible/filter_plugins/commonfilters.py index e61e98f4..ab31d7e2 100644 --- a/src/ops/ansible/filter_plugins/commonfilters.py +++ b/src/ops/ansible/filter_plugins/commonfilters.py @@ -35,7 +35,7 @@ def read_consul(key_path, consul_url="http://localhost:8500", recurse=True, show ret = sc.get(key_path,recurse) except Exception as e: if show_error: - ret['error'] = e.message + ret['error'] = str(e) return ret def read_envvar(varname, default=None): @@ -50,7 +50,7 @@ def read_yaml(fname, show_error=False): ret = y.safe_load(f.read()) except Exception as e: if show_error: - ret['error'] = e.message + ret['error'] = str(e) return ret def flatten_tree(d, parent_key='', sep='/'): @@ -138,7 +138,7 @@ def escape_json(input): return escaped[1:-1] return escaped -class FilterModule(object): +class FilterModule: def filters(self): return { diff --git a/src/ops/ansible/vars_plugins/clusterconfig.py b/src/ops/ansible/vars_plugins/clusterconfig.py index 15eb4dbc..1e777016 100644 --- a/src/ops/ansible/vars_plugins/clusterconfig.py +++ b/src/ops/ansible/vars_plugins/clusterconfig.py @@ -25,7 +25,7 @@ class VarsModule(BaseVarsPlugin): def __init__(self, *args): """ constructor """ - super(VarsModule, self).__init__(*args) + super().__init__(*args) logger.debug("Running plugin: %s with cluster config %s" % (__file__, os.environ['OPS_CLUSTER_CONFIG'])) @@ -33,5 +33,5 @@ def __init__(self, *args): self.config = app.cluster_config.all() def get_vars(self, loader, path, entities, cache=True): - super(VarsModule, self).get_vars(loader, path, entities) + super().get_vars(loader, path, entities) return self.config diff --git a/src/ops/ansible/vars_plugins/opsconfig.py b/src/ops/ansible/vars_plugins/opsconfig.py index afc94ea0..d0fa58e3 100644 --- a/src/ops/ansible/vars_plugins/opsconfig.py +++ b/src/ops/ansible/vars_plugins/opsconfig.py @@ -25,7 +25,7 @@ class VarsModule(BaseVarsPlugin): def __init__(self, *args): """ constructor """ - super(VarsModule, self).__init__(*args) + super().__init__(*args) logger.debug("Running plugin: %s with cluster config %s" % (__file__, os.environ['OPS_CLUSTER_CONFIG'])) @@ -37,5 +37,5 @@ def __init__(self, *args): }) def get_vars(self, loader, path, entities, cache=True): - super(VarsModule, self).get_vars(loader, path, entities) + super().get_vars(loader, path, entities) return self.config diff --git a/src/ops/cli/__init__.py b/src/ops/cli/__init__.py index 65fd271b..5abf48bc 100644 --- a/src/ops/cli/__init__.py +++ b/src/ops/cli/__init__.py @@ -40,7 +40,7 @@ def get_config_value(config, key): return config[key] except KeyError as e: err("You must set the %s value in %s.yaml or in the cli as an extra variable: -e %s=value" % - (e.message, config['cluster'], e.message)) + (str(e), config['cluster'], str(e))) sys.exit(1) def check_if_teleport_binary_installed(): diff --git a/src/ops/cli/config.py b/src/ops/cli/config.py index c46c074e..efb9f1e3 100644 --- a/src/ops/cli/config.py +++ b/src/ops/cli/config.py @@ -67,7 +67,7 @@ def load_extra_vars(loader): return extra_vars -class ClusterConfig(object): +class ClusterConfig: def __init__(self, cluster_config_generator, ops_config, cluster_config_path): """ @@ -130,7 +130,7 @@ def load_ssh_keys(self, cluster_config_path): self.has_ssh_keys = False -class JinjaConfigGenerator(object): +class JinjaConfigGenerator: def __init__(self, console_args, cluster_config_path, template): self.cluster_config_path = cluster_config_path self.console_args = console_args @@ -162,7 +162,7 @@ def get(self): return yaml.safe_load(rendered) -class ClusterConfigGenerator(object): +class ClusterConfigGenerator: def __init__(self, console_args, cluster_config_path, template): self.template = template self.cluster_config_path = cluster_config_path diff --git a/src/ops/cli/config_generator.py b/src/ops/cli/config_generator.py index d761bf77..28e02e45 100644 --- a/src/ops/cli/config_generator.py +++ b/src/ops/cli/config_generator.py @@ -32,7 +32,7 @@ def get_epilog(self): ''' -class ConfigGeneratorRunner(object): +class ConfigGeneratorRunner: def __init__(self, cluster_config_path): self.cluster_config_path = cluster_config_path diff --git a/src/ops/cli/helmfile.py b/src/ops/cli/helmfile.py index d468e9b4..01413b8a 100644 --- a/src/ops/cli/helmfile.py +++ b/src/ops/cli/helmfile.py @@ -49,9 +49,9 @@ def get_epilog(self): ''' -class HelmfileRunner(CompositionConfigGenerator, object): +class HelmfileRunner(CompositionConfigGenerator): def __init__(self, ops_config, cluster_config_path, execute): - super(HelmfileRunner, self).__init__(["helmfiles"]) + super().__init__(["helmfiles"]) logging.basicConfig(level=logging.INFO) self.ops_config = ops_config self.cluster_config_path = cluster_config_path diff --git a/src/ops/cli/inventory.py b/src/ops/cli/inventory.py index 7fe55eab..de54bdce 100644 --- a/src/ops/cli/inventory.py +++ b/src/ops/cli/inventory.py @@ -39,7 +39,7 @@ def configure(self, parser): return parser -class InventoryRunner(object): +class InventoryRunner: def __init__(self, ansible_inventory, cluster_name): """ :type ansible_inventory: ops.inventory.generator.AnsibleInventory diff --git a/src/ops/cli/packer.py b/src/ops/cli/packer.py index 351c2494..13fc5ee7 100644 --- a/src/ops/cli/packer.py +++ b/src/ops/cli/packer.py @@ -36,7 +36,7 @@ def get_epilog(self): ''' -class PackerRunner(object): +class PackerRunner: def __init__(self, root_dir, cluster_config): self.cluster_config = cluster_config self.root_dir = root_dir diff --git a/src/ops/cli/parser.py b/src/ops/cli/parser.py index 315cdfeb..9e7b2f57 100644 --- a/src/ops/cli/parser.py +++ b/src/ops/cli/parser.py @@ -11,7 +11,7 @@ import argparse -class RootParser(object): +class RootParser: def __init__(self, sub_parsers=None): """ :type sub_parsers: list[SubParserConfig] @@ -55,7 +55,7 @@ def parse_known_args(self, args=None): return self._get_parser().parse_known_args(args) -class SubParserConfig(object): +class SubParserConfig: def get_name(self): pass diff --git a/src/ops/cli/playbook.py b/src/ops/cli/playbook.py index a0069b3d..57ca1790 100644 --- a/src/ops/cli/playbook.py +++ b/src/ops/cli/playbook.py @@ -57,7 +57,7 @@ def get_help(self): return 'Run an Ansible playbook' -class PlaybookRunner(object): +class PlaybookRunner: def __init__(self, ops_config, root_dir, inventory_generator, cluster_config_path, cluster_config): """ diff --git a/src/ops/cli/run.py b/src/ops/cli/run.py index c1296332..af198f7d 100644 --- a/src/ops/cli/run.py +++ b/src/ops/cli/run.py @@ -53,7 +53,7 @@ def get_name(self): return 'run' -class CommandRunner(object): +class CommandRunner: def __init__(self, ops_config, root_dir, inventory_generator, cluster_config_path, cluster_config): diff --git a/src/ops/cli/ssh.py b/src/ops/cli/ssh.py index 2785369d..768ce2a6 100644 --- a/src/ops/cli/ssh.py +++ b/src/ops/cli/ssh.py @@ -151,7 +151,7 @@ def get_epilog(self): ''' -class SshRunner(object): +class SshRunner: def __init__(self, cluster_config_path, cluster_config, ansible_inventory, ops_config, cluster_name, root_dir): @@ -444,7 +444,7 @@ def get_host_ip(self, args, host): -class SshConfig(object): +class SshConfig: def __init__(self, scb_enabled, teleport_enabled, ssh_config_prop, ssh_user, ssh_host, ssh_host_dest, ssh_host_bastion, scb_host, scb_ssh_host, host, scb_proxy_port): self.scb_enabled = scb_enabled diff --git a/src/ops/cli/sync.py b/src/ops/cli/sync.py index c7ef560b..3ab75bbf 100644 --- a/src/ops/cli/sync.py +++ b/src/ops/cli/sync.py @@ -64,7 +64,7 @@ def get_epilog(self): """ -class SyncRunner(object): +class SyncRunner: def __init__(self, cluster_config, root_dir, ansible_inventory, inventory_generator, ops_config): @@ -159,7 +159,7 @@ def execute_rsync_scp(self, args, src, dest, ssh_user, ssh_host, ssh_config_path def is_teleport_enabled(self, args): return True if self.cluster_config.get('teleport', {}).get('enabled') and args.use_teleport else False -class PathExpr(object): +class PathExpr: def __init__(self, path): self._path = path diff --git a/src/ops/cli/terraform.py b/src/ops/cli/terraform.py index dfaa3a66..2fa68305 100644 --- a/src/ops/cli/terraform.py +++ b/src/ops/cli/terraform.py @@ -151,7 +151,7 @@ def get_epilog(self): ''' -class TerraformRunner(object): +class TerraformRunner: def __init__(self, root_dir, cluster_config_path, cluster_config, inventory_generator, ops_config, template, execute): self.cluster_config_path = cluster_config_path diff --git a/src/ops/hierarchical/composition_config_generator.py b/src/ops/hierarchical/composition_config_generator.py index e0e3af40..b3fe86bf 100644 --- a/src/ops/hierarchical/composition_config_generator.py +++ b/src/ops/hierarchical/composition_config_generator.py @@ -75,10 +75,10 @@ def get_terraform_path_for_composition(self, path_prefix, composition): prefix, composition) -class TerraformConfigGenerator(CompositionConfigGenerator, object): +class TerraformConfigGenerator(CompositionConfigGenerator): def __init__(self, composition_order, excluded_config_keys): - super(TerraformConfigGenerator, self).__init__(composition_order) + super().__init__(composition_order) self.excluded_config_keys = excluded_config_keys def generate_files(self, config_path, composition_path, composition): @@ -118,7 +118,7 @@ def generate_variables_config(self, composition, config_path, composition_path): print_data=True) -class CompositionSorter(object): +class CompositionSorter: def __init__(self, composition_order): self.composition_order = composition_order @@ -130,7 +130,7 @@ def get_sorted_compositions(self, compositions, reverse=False): return tuple(reversed(result)) if reverse else result -class HierarchicalConfigGenerator(object): +class HierarchicalConfigGenerator: def __init__(self): self.config_processor = ConfigProcessor() diff --git a/src/ops/inventory/SKMS.py b/src/ops/inventory/SKMS.py index 0ded6d11..6f6495fa 100644 --- a/src/ops/inventory/SKMS.py +++ b/src/ops/inventory/SKMS.py @@ -30,7 +30,7 @@ from os.path import expanduser -class WebApiClient(object): +class WebApiClient: """Class to allow easy access to the SKMS Web API""" # Version Constants CLIENT_TYPE = "python3.12.requests" diff --git a/src/ops/inventory/azurerm.py b/src/ops/inventory/azurerm.py index 50a79c53..afb785a4 100644 --- a/src/ops/inventory/azurerm.py +++ b/src/ops/inventory/azurerm.py @@ -247,7 +247,7 @@ def azure_id_to_dict(id): return result -class AzureRM(object): +class AzureRM: def __init__(self, args): self._args = args @@ -405,7 +405,7 @@ def compute_client(self): return self._compute_client -class AzureInventory(object): +class AzureInventory: def __init__(self): diff --git a/src/ops/inventory/ec2inventory.py b/src/ops/inventory/ec2inventory.py index b474221d..f0778717 100644 --- a/src/ops/inventory/ec2inventory.py +++ b/src/ops/inventory/ec2inventory.py @@ -16,7 +16,7 @@ from botocore.exceptions import NoRegionError, NoCredentialsError, PartialCredentialsError -class Ec2Inventory(object): +class Ec2Inventory: @staticmethod diff --git a/src/ops/inventory/generator.py b/src/ops/inventory/generator.py index 39ef1069..1af677d3 100644 --- a/src/ops/inventory/generator.py +++ b/src/ops/inventory/generator.py @@ -27,7 +27,7 @@ logger = logging.getLogger(__name__) -class CachedInventoryGenerator(object): +class CachedInventoryGenerator: def __init__(self, base_inventory_generator, cluster_config, ops_config): self.inventory_generator = base_inventory_generator self.ops_config = ops_config @@ -109,7 +109,7 @@ def generate(self): inventory_path, ssh_config_path, self.inventory_generator.errors) -class InventoryGenerator(object): +class InventoryGenerator: def __init__(self, cluster_config, ssh_config_generator, ops_config, inventory_generators=[]): self.ssh_config_generator = ssh_config_generator @@ -188,7 +188,7 @@ def display_errors(errors): color='yellow') -class DirInventoryGenerator(object): +class DirInventoryGenerator: """ Just copies the full path specified in path into the inventory dir """ def __init__(self, root_dir): @@ -201,7 +201,7 @@ def generate(self, dest, config): shutil.copytree(self.root_dir + '/' + config['directory'], dest, dirs_exist_ok=True) -class PluginInventoryGenerator(object): +class PluginInventoryGenerator: template = """#!/usr/bin/env python # CONFIG: {config} # PLUGIN PATH: {plugin_path} @@ -236,7 +236,7 @@ def generate(self, dest, config): os.fchmod(f.fileno(), 0o500) -class ShellInventoryGenerator(object): +class ShellInventoryGenerator: """ Creates a script to be executed by Ansible inventory mechanism and places it in the temp directory @@ -289,7 +289,7 @@ def generate(self, dest, config): os.fchmod(f.fileno(), 0o500) -class AnsibleInventory(object): +class AnsibleInventory: def __init__(self, inventory_generator): """ diff --git a/src/ops/inventory/plugin/azr.py b/src/ops/inventory/plugin/azr.py index 3209b1fa..0443b0d2 100644 --- a/src/ops/inventory/plugin/azr.py +++ b/src/ops/inventory/plugin/azr.py @@ -12,7 +12,7 @@ from ops.inventory.azurerm import * from ansible.playbook.play import display -class DictGlue(object): +class DictGlue: def __init__(self, data={}): self.__dict__.update(data) diff --git a/src/ops/inventory/sshconfig.py b/src/ops/inventory/sshconfig.py index f879f378..14590bfc 100644 --- a/src/ops/inventory/sshconfig.py +++ b/src/ops/inventory/sshconfig.py @@ -16,7 +16,7 @@ from . import check_if_teleport_binary_installed -class SshConfigGenerator(object): +class SshConfigGenerator: SSH_CONFIG_FILE = "ssh.config" SSH_SCB_PROXY_TPL_FILE = "ssh.scb.proxy.config.tpl" SSH_TELEPORT_PROXY_TPL_FILE = "ssh.teleport.config.tpl" diff --git a/src/ops/jinja/__init__.py b/src/ops/jinja/__init__.py index 09b242c5..39a660b5 100644 --- a/src/ops/jinja/__init__.py +++ b/src/ops/jinja/__init__.py @@ -14,7 +14,7 @@ from ansible.plugins.loader import PluginLoader -class Template(object): +class Template: def __init__(self, root_dir, ops_config): loader = ChoiceLoader([ diff --git a/src/ops/main.py b/src/ops/main.py index 3ecf2e86..fee3ef04 100644 --- a/src/ops/main.py +++ b/src/ops/main.py @@ -47,7 +47,7 @@ def configure_logging(args): class AppContainer(Container): def __init__(self, argv=None): - super(AppContainer, self).__init__() + super().__init__() self.argv = instance(argv) diff --git a/src/ops/opsconfig.py b/src/ops/opsconfig.py index 6a429162..1c9fb0b2 100644 --- a/src/ops/opsconfig.py +++ b/src/ops/opsconfig.py @@ -31,7 +31,7 @@ def file_tree(config_path, search_fname): return file_stack -class OpsConfig(object): +class OpsConfig: """ Parses the all .opsconfig.yaml files that it can find starting from the first down the path to the one in the current dir diff --git a/src/ops/simpleconsul.py b/src/ops/simpleconsul.py index 193ef8d8..da89d12e 100644 --- a/src/ops/simpleconsul.py +++ b/src/ops/simpleconsul.py @@ -32,7 +32,7 @@ } -class SimpleConsul(object): +class SimpleConsul: """ Simple wrapper class for interacting with Consul. Focused mainly on KV operations""" consul_params = {} diff --git a/src/ops/simplessm.py b/src/ops/simplessm.py index cb3e884e..a99fb622 100644 --- a/src/ops/simplessm.py +++ b/src/ops/simplessm.py @@ -15,7 +15,7 @@ import os -class SimpleSSM(object): +class SimpleSSM: def __init__(self, aws_profile, region_name): self.initial_aws_profile = os.getenv('AWS_PROFILE', None) self.aws_profile = aws_profile diff --git a/src/ops/simplevault.py b/src/ops/simplevault.py index 8ff9f81c..28f1063c 100644 --- a/src/ops/simplevault.py +++ b/src/ops/simplevault.py @@ -29,7 +29,7 @@ MAX_LDAP_ATTEMPTS = 3 -class SimpleVault(object): +class SimpleVault: p_vault_conn = None # persistent vault connection @@ -233,7 +233,7 @@ def is_authenticated(self): return self.vault_conn.is_authenticated() -class ManagedVaultSecret(object): +class ManagedVaultSecret: p_sv = None # Persistent SimpleVault accessory object @@ -274,14 +274,14 @@ def __init__( except Exception as e: display( 'MANAGED-SECRET: could not obtain a proper' - ' Vault connection.\n{}'.format(e.message) + ' Vault connection.\n{}'.format(str(e)) ) raise e try: self.current_data = self.sv.get(path=path, fetch_all=True) except Exception as e: display('MANAGED-SECRET: could not confirm if secret at path {} does or not already exist. ' - 'Exception was:\n{}'.format(path, e.message)) + 'Exception was:\n{}'.format(path, str(e))) raise e if self.current_data.get(key): # something exists on that path, we assume the secret already diff --git a/src/ops/terraform/terraform_cmd_generator.py b/src/ops/terraform/terraform_cmd_generator.py index 1d231e4a..3108823e 100644 --- a/src/ops/terraform/terraform_cmd_generator.py +++ b/src/ops/terraform/terraform_cmd_generator.py @@ -18,7 +18,7 @@ from ops.cli import err, display -class TerraformCommandGenerator(object): +class TerraformCommandGenerator: def __init__(self, root_dir, cluster_config, inventory_generator, ops_config, template): self.cluster_config = cluster_config diff --git a/tests/e2e/fixture/ansible/plugins/filter_plugins/filters.py b/tests/e2e/fixture/ansible/plugins/filter_plugins/filters.py index 84779abf..14354bf2 100644 --- a/tests/e2e/fixture/ansible/plugins/filter_plugins/filters.py +++ b/tests/e2e/fixture/ansible/plugins/filter_plugins/filters.py @@ -12,7 +12,7 @@ def my_filter(string): return 'filtered: ' + string -class FilterModule(object): +class FilterModule: def filters(self): return { 'my_filter': my_filter From ea60c970d20f6d0f974f831aa97091f562782315 Mon Sep 17 00:00:00 2001 From: Silviu BURCEA <2476082+silviuburceadev@users.noreply.github.com> Date: Wed, 8 Jul 2026 11:31:04 +0300 Subject: [PATCH 18/18] [DOCS] Add Python 3.13/3.14 in the compatibility list --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index a917adf8..7b275813 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,8 @@ 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',