Skip to content

Commit a2c20e8

Browse files
committed
fix: ensure cr resource in hybrid strategy
(cherry picked from commit e505ef01a11e4eaa8015076804b82cec02c7ef1a)
1 parent baa8ac2 commit a2c20e8

File tree

3 files changed

+91
-75
lines changed

3 files changed

+91
-75
lines changed

agentkit/toolkit/strategies/hybrid_strategy.py

Lines changed: 89 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,22 @@ def build(self, common_config: CommonConfig, strategy_config: HybridStrategyConf
120120
cr_repo_name = self._prepare_cr_config(strategy_config.cr_repo_name, common_config.agent_name)
121121
if cr_repo_name != strategy_config.cr_repo_name:
122122
config_updates.add('cr_repo_name', cr_repo_name)
123-
123+
124124
should_push, reason = self._should_push_to_cr(strategy_config, cr_repo_name)
125125
if should_push:
126-
cr_updates = self._handle_cr_push(result, strategy_config, cr_repo_name)
126+
cr_updates = self._handle_cr_push(common_config, result, strategy_config, cr_repo_name)
127127
config_updates.merge(cr_updates)
128128
else:
129129
self._report_cr_skip_reason(reason, strategy_config)
130-
130+
131+
# Persist local build metadata
132+
if result.build_timestamp:
133+
config_updates.add('build_timestamp', result.build_timestamp.isoformat())
134+
if result.image:
135+
config_updates.add('full_image_name', result.image.full_name)
136+
if getattr(result.image, 'digest', None):
137+
config_updates.add('image_id', result.image.digest)
138+
131139
result.config_updates = config_updates if config_updates.has_updates() else None
132140
return result
133141

@@ -235,21 +243,28 @@ def _should_push_to_cr(self, strategy_config: HybridStrategyConfig, cr_repo_name
235243
Returns:
236244
Tuple of (should_push: bool, reason: str).
237245
"""
246+
# Validate instance name
238247
if not strategy_config.cr_instance_name:
239248
return False, "CR instance name is empty"
240-
241249
if strategy_config.cr_instance_name == AUTO_CREATE_VE:
242250
return False, "CR instance name is 'Auto'"
243-
244-
if '{{' in strategy_config.cr_instance_name:
251+
if '{{' in (strategy_config.cr_instance_name or ''):
245252
return False, "CR instance name contains unrendered template variables"
246-
253+
254+
# Validate namespace name
255+
if not strategy_config.cr_namespace_name:
256+
return False, "CR namespace name is empty"
257+
if strategy_config.cr_namespace_name == AUTO_CREATE_VE:
258+
return False, "CR namespace name is 'Auto'"
259+
if '{{' in (strategy_config.cr_namespace_name or ''):
260+
return False, "CR namespace name contains unrendered template variables"
261+
247262
if not cr_repo_name:
248263
return False, "CR repository name is empty"
249264

250265
return True, ""
251266

252-
def _handle_cr_push(self, result: BuildResult, strategy_config: HybridStrategyConfig,
267+
def _handle_cr_push(self, common_config: CommonConfig, result: BuildResult, strategy_config: HybridStrategyConfig,
253268
cr_repo_name: str) -> 'ConfigUpdates':
254269
"""
255270
Handle pushing image to Container Registry.
@@ -272,17 +287,48 @@ def _handle_cr_push(self, result: BuildResult, strategy_config: HybridStrategyCo
272287

273288
config_updates = ConfigUpdates()
274289

275-
local_image = result.image.full_name if result.image else None
276-
if not local_image:
290+
# Use image_id (SHA) from build result for pushing
291+
image_id = result.image.digest if result.image else None
292+
if not image_id:
277293
return config_updates
278-
279-
cr_image_url = self._push_to_cr(
280-
local_image,
281-
strategy_config.cr_instance_name,
282-
strategy_config.cr_namespace_name,
283-
cr_repo_name,
284-
strategy_config.image_tag
294+
295+
# Ensure CR resources exist (instance/namespace/repo)
296+
cr_cfg = CRServiceConfig(
297+
instance_name=strategy_config.cr_instance_name,
298+
namespace_name=strategy_config.cr_namespace_name,
299+
repo_name=cr_repo_name
300+
)
301+
cr_service = CRService(reporter=self.reporter)
302+
ensure_result = cr_service.ensure_cr_resources(cr_cfg, common_config=common_config)
303+
if not ensure_result.success:
304+
raise Exception(ensure_result.error or "Failed to ensure CR resources")
305+
306+
# Ensure public endpoint is enabled for CR instance
307+
public_result = cr_service.ensure_public_endpoint(cr_cfg)
308+
if not public_result.success:
309+
raise Exception(public_result.error or "Failed to enable CR public endpoint")
310+
311+
# Write back any auto-created names to config
312+
if ensure_result.instance_name and ensure_result.instance_name != strategy_config.cr_instance_name:
313+
config_updates.add('cr_instance_name', ensure_result.instance_name)
314+
if ensure_result.namespace_name and ensure_result.namespace_name != strategy_config.cr_namespace_name:
315+
config_updates.add('cr_namespace_name', ensure_result.namespace_name)
316+
if ensure_result.repo_name and ensure_result.repo_name != cr_repo_name:
317+
config_updates.add('cr_repo_name', ensure_result.repo_name)
318+
319+
# Push image (inline call to CRService.login_and_push_image)
320+
self.reporter.info(
321+
f"Pushing image to CR: {cr_cfg.instance_name}/{cr_cfg.namespace_name}/{cr_cfg.repo_name}:{strategy_config.image_tag}"
285322
)
323+
success, push_result = cr_service.login_and_push_image(
324+
cr_config=cr_cfg,
325+
image_id=image_id,
326+
image_tag=strategy_config.image_tag,
327+
namespace=cr_cfg.namespace_name
328+
)
329+
if not success:
330+
raise Exception(f"Image push failed: {push_result}")
331+
cr_image_url = push_result
286332

287333
config_updates.add('cr_image_full_url', cr_image_url)
288334

@@ -296,14 +342,14 @@ def _handle_cr_push(self, result: BuildResult, strategy_config: HybridStrategyCo
296342

297343
def _report_cr_skip_reason(self, reason: str, strategy_config: HybridStrategyConfig) -> None:
298344
"""Report reason for skipping CR push."""
299-
if '{{' in strategy_config.cr_instance_name:
300-
self.reporter.warning(f"⚠️ CR instance name contains unrendered template variables: {strategy_config.cr_instance_name}")
301-
self.reporter.warning("⚠️ Ensure volcenginesdkcore is installed to render template variables")
345+
if '{{' in (strategy_config.cr_instance_name or '') or '{{' in (strategy_config.cr_namespace_name or ''):
346+
self.reporter.warning("CR names contain unrendered template variables")
347+
self.reporter.warning("Ensure Volcengine AK/SK are configured and STS can fetch account_id for template rendering.")
302348
elif strategy_config.cr_instance_name == AUTO_CREATE_VE:
303-
self.reporter.warning("⚠️ CR instance name is 'Auto', skipping push to CR")
304-
self.reporter.warning("⚠️ Use 'agentkit config' to configure a valid CR instance name")
349+
self.reporter.warning("CR instance name is 'Auto', skipping push to CR")
350+
self.reporter.warning("Use 'agentkit config' to configure a valid CR instance name")
305351
else:
306-
self.reporter.warning(f"⚠️ Invalid CR configuration, skipping push to CR: {reason}")
352+
self.reporter.warning(f"Invalid CR configuration, skipping push to CR: {reason}")
307353

308354
def _validate_cr_image_url(self, strategy_config: HybridStrategyConfig) -> DeployResult:
309355
"""
@@ -316,42 +362,54 @@ def _validate_cr_image_url(self, strategy_config: HybridStrategyConfig) -> Deplo
316362
if image_url:
317363
return DeployResult(success=True)
318364

319-
if '{{' in strategy_config.cr_instance_name:
365+
from agentkit.toolkit.errors import ErrorCode
366+
367+
if '{{' in (strategy_config.cr_instance_name or '') or '{{' in (strategy_config.cr_namespace_name or ''):
320368
error_msg = (
321-
f"CR instance name contains unrendered template variables: {strategy_config.cr_instance_name}\n"
322-
f"Ensure volcenginesdkcore is installed to render template variables."
369+
"CR names contain unrendered template variables. Ensure Volcengine AK/SK are configured and STS can fetch account_id."
323370
)
371+
error_code = ErrorCode.CONFIG_INVALID
324372
elif strategy_config.cr_instance_name == AUTO_CREATE_VE or not strategy_config.cr_instance_name:
325373
error_msg = (
326374
f"Hybrid mode requires valid CR configuration. Current cr_instance_name='{strategy_config.cr_instance_name}' is invalid.\n"
327375
f"Use 'agentkit config' to configure a valid CR instance name, or switch to local/cloud mode."
328376
)
377+
error_code = ErrorCode.CONFIG_INVALID
329378
else:
330379
error_msg = (
331380
"CR image URL not found. Run 'agentkit build' to build and push the image to CR."
332381
)
382+
error_code = ErrorCode.RESOURCE_NOT_FOUND
333383

334384
return DeployResult(
335385
success=False,
336386
error=error_msg,
337-
error_code="INVALID_IMAGE_URL"
387+
error_code=error_code
338388
)
339389

340390
def _to_builder_config(self, common_config: CommonConfig,
341391
strategy_config: HybridStrategyConfig) -> LocalDockerBuilderConfig:
342392
"""
343-
Convert HybridVeAgentkitConfig to LocalDockerBuilderConfig.
393+
Convert HybridStrategyConfig to LocalDockerBuilderConfig.
344394
"""
395+
# Retrieve Docker build config from manager (contains CLI runtime options)
396+
docker_build_config = None
397+
if self.config_manager:
398+
try:
399+
docker_build_config = self.config_manager.get_docker_build_config()
400+
except Exception:
401+
docker_build_config = None
345402
return LocalDockerBuilderConfig(
346403
common_config=common_config,
347404
image_name=common_config.agent_name or "agentkit-app",
348-
image_tag=strategy_config.image_tag
405+
image_tag=strategy_config.image_tag,
406+
docker_build_config=docker_build_config
349407
)
350408

351409
def _to_runner_config(self, common_config: CommonConfig,
352410
strategy_config: HybridStrategyConfig) -> VeAgentkitRunnerConfig:
353411
"""
354-
Convert HybridVeAgentkitConfig to VeAgentkitRunnerConfig.
412+
Convert HybridStrategyConfig to VeAgentkitRunnerConfig.
355413
"""
356414
merged_envs = merge_runtime_envs(common_config, strategy_config.to_dict())
357415

@@ -367,46 +425,4 @@ def _to_runner_config(self, common_config: CommonConfig,
367425
image_url=strategy_config.cr_image_full_url
368426
)
369427

370-
def _push_to_cr(self, local_image: str, cr_instance: str,
371-
cr_namespace: str, cr_repo: str, tag: str) -> str:
372-
"""
373-
Push local image to Container Registry.
374-
375-
Args:
376-
local_image: Local image name (e.g., agentkit-app:v1.0).
377-
cr_instance: CR instance name.
378-
cr_namespace: CR namespace.
379-
cr_repo: CR repository name.
380-
tag: Image tag.
381-
382-
Returns:
383-
Full CR image URL.
384-
385-
Raises:
386-
Exception: If image push fails. Exceptions are handled by Executor.
387-
"""
388-
cr_config = CRServiceConfig(
389-
instance_name=cr_instance,
390-
namespace_name=cr_namespace,
391-
repo_name=cr_repo
392-
)
393-
394-
cr_service = CRService(reporter=self.reporter)
395-
396-
import docker
397-
client = docker.from_env()
398-
image = client.images.get(local_image)
399-
image_id = image.id
400-
401-
self.reporter.info(f"Pushing image to CR: {cr_instance}/{cr_namespace}/{cr_repo}:{tag}")
402-
success, result = cr_service.login_and_push_image(
403-
cr_config=cr_config,
404-
image_id=image_id,
405-
image_tag=tag,
406-
namespace=cr_namespace
407-
)
408-
409-
if not success:
410-
raise Exception(f"Image push failed: {result}")
411-
412-
return result
428+
# _push_to_cr removed: logic is handled inline within _handle_cr_push

agentkit/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
VERSION = "0.1.12"
15+
VERSION = "0.1.13"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "agentkit-sdk-python"
3-
version = "0.1.12"
3+
version = "0.1.13"
44
description = "Python SDK for transforming any AI agent into a production-ready application. Framework-agnostic primitives for runtime, memory, authentication, and tools with volcengine-managed infrastructure."
55
readme = "README.md"
66
requires-python = ">=3.10"

0 commit comments

Comments
 (0)