Skip to content

Commit 49e93ac

Browse files
authored
Provider version pinning (#45)
* chore: remove unnecessary package * feat: Support provider version pinning * Upgrade stackql-deploy version
1 parent 5a767a1 commit 49e93ac

File tree

5 files changed

+105
-15
lines changed

5 files changed

+105
-15
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 1.7.7 (2024-10-09)
4+
5+
- Supported version pinning for providers(aws, gcp, azure and etc) in `manifest` file
6+
37
## 1.7.6 (2024-10-07)
48

59
- Added support for named `exports` (assigning an alias to the column name in the resource query file) - allows for more generalization and reuse of query files

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
setup(
1212
name='stackql-deploy',
13-
version='1.7.6',
13+
version='1.7.7',
1414
description='Model driven resource provisioning and deployment framework using StackQL.',
1515
long_description=readme,
1616
long_description_content_type='text/x-rst',

stackql_deploy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.7.6'
1+
__version__ = '1.7.7'

stackql_deploy/cmd/base.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from ..lib.utils import perform_retries, run_stackql_command, catch_error_and_exit, run_stackql_query, export_vars, show_query, get_type, check_all_dicts
2-
from ..lib.config import setup_environment, load_manifest, get_global_context_and_providers, get_full_context
3-
from ..lib.templating import get_queries
1+
from ..lib.utils import perform_retries, run_stackql_command, catch_error_and_exit, run_stackql_query, export_vars, show_query, check_all_dicts
2+
from ..lib.config import setup_environment, load_manifest, get_global_context_and_providers
43

54
class StackQLBase:
65
def __init__(self, stackql, vars, logger, stack_dir, stack_env):

stackql_deploy/lib/utils.py

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import time, json, sys, subprocess
1+
import time
2+
import json
3+
import sys
4+
import subprocess
5+
import re
26

37
def catch_error_and_exit(errmsg, logger):
48
logger.error(errmsg)
@@ -81,6 +85,17 @@ def run_stackql_command(command, stackql, logger, ignore_errors=False, retries=0
8185
while attempt <= retries:
8286
try:
8387
logger.debug(f"(utils.run_stackql_command) executing stackql command (attempt {attempt + 1}):\n\n{command}\n")
88+
# If qyery is start with 'REGISTRY PULL', check version
89+
if command.startswith("REGISTRY PULL"):
90+
match = re.match(r'(REGISTRY PULL \w+)(::v[\d\.]+)?', command)
91+
if match:
92+
service_provider = match.group(1)
93+
version = match.group(2)
94+
if version:
95+
command = f"{service_provider} {version[2:]}"
96+
else:
97+
raise ValueError("REGISTRY PULL command must be in the format 'REGISTRY PULL <service_provider>::v<version>' or 'REGISTRY PULL <service_provider>'")
98+
8499
result = stackql.executeStmt(command)
85100
logger.debug(f"(utils.run_stackql_command) stackql command result:\n\n{result}, type: {type(result)}\n")
86101

@@ -115,17 +130,89 @@ def run_stackql_command(command, stackql, logger, ignore_errors=False, retries=0
115130
def pull_providers(providers, stackql, logger):
116131
logger.debug(f"(utils.pull_providers) stackql run time info:\n\n{json.dumps(stackql.properties(), indent=2)}\n")
117132
installed_providers = run_stackql_query("SHOW PROVIDERS", stackql, False, logger) # not expecting an error here
118-
if len(installed_providers) == 0:
119-
installed_names = set()
120-
else:
121-
installed_names = {provider["name"] for provider in installed_providers}
133+
# check if the provider is already installed
122134
for provider in providers:
123-
if provider not in installed_names:
124-
logger.info(f"pulling provider '{provider}'...")
125-
msg = run_stackql_command(f"REGISTRY PULL {provider}", stackql, logger)
126-
logger.info(msg)
135+
# check if the provider is a specific version
136+
if "::" in provider:
137+
name, version = provider.split("::")
138+
check_provider_version_available(name, version, stackql, logger)
139+
found = False
140+
# provider is a version which will be installed
141+
# installed is a version which is already installed
142+
for installed in installed_providers:
143+
# if name and version are the same, it's already installed
144+
if installed["name"] == name and installed["version"] == version:
145+
logger.info(f"provider '{provider}' is already installed.")
146+
found = True
147+
break
148+
# if name is the same but the installed version is higher,
149+
# it's already installed(latest version)
150+
elif installed["name"] == name and is_installed_version_higher(installed["version"], version):
151+
logger.warning(f"provider '{name}' version '{version}' is not available in the registry, but a higher version '{installed['version']}' is already installed.")
152+
logger.warning("If you want to install the lower version, you must delete the higher version folder from the stackql providers directory.")
153+
logger.info(f"provider {name}::{version} is already installed.")
154+
found = True
155+
break
156+
# if not found, pull the provider
157+
if not found:
158+
logger.info(f"pulling provider '{provider}'...")
159+
msg = run_stackql_command(f"REGISTRY PULL {provider}", stackql, logger)
160+
logger.info(msg)
161+
else:
162+
found = False
163+
# provider is a name which will be installed
164+
# installed is a list of providers which are already installed
165+
for installed in installed_providers:
166+
if installed["name"] == provider:
167+
logger.info(f"provider '{provider}' is already installed.")
168+
found = True
169+
break
170+
# if not found, pull the provider
171+
if not found:
172+
logger.info(f"pulling provider '{provider}'...")
173+
msg = run_stackql_command(f"REGISTRY PULL {provider}", stackql, logger)
174+
logger.info(msg)
175+
176+
def check_provider_version_available(provider_name, version, stackql, logger):
177+
"""Check if the provider version is available in the registry.
178+
179+
Args:
180+
provider_name (str): The name of the provider.
181+
version (str): The version of the provider.
182+
stackql (StackQL): The StackQL object.
183+
logger (Logger): The logger object.
184+
"""
185+
query = f"REGISTRY LIST {provider_name}"
186+
try:
187+
result = run_stackql_query(query, stackql, True, logger)
188+
# result[0]['versions'] is a string, not a list
189+
# so we need to split it into a list
190+
versions = result[0]['versions'].split(", ")
191+
if version not in versions:
192+
catch_error_and_exit(f"(utils.check_provider_version_available) version '{version}' not found for provider '{provider_name}', available versions: {versions}", logger)
193+
except Exception:
194+
catch_error_and_exit(f"(utils.check_provider_version_available) provider '{provider_name}' not found in registry", logger)
195+
196+
def is_installed_version_higher(installed_version, requested_version):
197+
"""Check if the installed version is higher than the requested version.
198+
199+
Args:
200+
installed_version (str): v24.09.00251
201+
requested_version (str): v23.01.00104
202+
203+
Returns:
204+
bool: True if installed version is higher than requested version, False otherwise
205+
"""
206+
207+
try:
208+
int_installed = int(installed_version.replace("v", "").replace(".", ""))
209+
int_requested = int(requested_version.replace("v", "").replace(".", ""))
210+
if int_installed > int_requested:
211+
return True
127212
else:
128-
logger.info(f"provider '{provider}' is already installed.")
213+
return False
214+
except Exception:
215+
catch_error_and_exit(f"(utils.is_installed_version_higher) version comparison failed: installed version '{installed_version}', requested version '{requested_version}'", logger)
129216

130217
def run_test(resource, rendered_test_iql, stackql, logger, delete_test=False):
131218
try:

0 commit comments

Comments
 (0)