Skip to content

Commit 0f53dc9

Browse files
committed
Merge branch 'develop' of ssh.gitlab.aws.dev:genaiic-reusable-assets/engagement-artifacts/genaiic-idp-accelerator into develop
2 parents 537e713 + 713e516 commit 0f53dc9

File tree

2 files changed

+188
-146
lines changed

2 files changed

+188
-146
lines changed

publish.py

Lines changed: 165 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,6 @@ def clean_and_build(self, template_path):
494494
"build",
495495
"--template-file",
496496
template_filename,
497-
"--cached",
498497
]
499498

500499
# Only add --parallel if no idp_common dependencies to prevent race conditions
@@ -504,17 +503,10 @@ def clean_and_build(self, template_path):
504503
if self.use_container_flag and self.use_container_flag.strip():
505504
cmd.append(self.use_container_flag)
506505

507-
result = subprocess.run(cmd, cwd=abs_dir_path)
506+
result = subprocess.run(cmd, capture_output=True, text=True)
508507
if result.returncode != 0:
509-
# If cached build fails, try without cache
510-
print(
511-
f"Cached build failed, retrying without cache for {template_filename}"
512-
)
513-
cmd_no_cache = [c for c in cmd if c != "--cached"]
514-
result = subprocess.run(cmd_no_cache, cwd=abs_dir_path)
515-
if result.returncode != 0:
516-
print("Error running sam build")
517-
sys.exit(1)
508+
print("Error running sam build")
509+
sys.exit(1)
518510

519511
def _check_template_for_idp_common_deps(self, template_path):
520512
"""Check if a template has Lambda functions with idp_common dependencies."""
@@ -552,15 +544,7 @@ def build_and_package_template(self, directory, force_rebuild=False):
552544
# Build the template from the pattern directory
553545
cmd = ["sam", "build", "--template-file", "template.yaml"]
554546

555-
# Add caching but remove parallel flag to avoid race conditions
556-
# when building multiple templates concurrently
557-
cmd.extend(
558-
[
559-
"--cached", # Enable SAM build caching
560-
# Note: Removed --parallel to prevent race conditions with idp_common_pkg
561-
]
562-
)
563-
547+
# Add container flag if needed
564548
if self.use_container_flag and self.use_container_flag.strip():
565549
cmd.append(self.use_container_flag)
566550

@@ -575,29 +559,10 @@ def build_and_package_template(self, directory, force_rebuild=False):
575559
sam_build_time = time.time() - sam_build_start
576560

577561
if result.returncode != 0:
578-
# If cached build fails, try without cache
579-
self.log_verbose(
580-
f"Cached build failed for {directory}, retrying without cache"
581-
)
582-
self.console.print(
583-
f"[yellow]Cached build failed for {directory}, retrying without cache[/yellow]"
584-
)
585-
cmd_no_cache = [c for c in cmd if c != "--cached"]
586-
sam_build_start = time.time()
587-
self.log_verbose(
588-
f"Running SAM build command (no cache) in {directory}: {' '.join(cmd_no_cache)}"
589-
)
590-
result = subprocess.run(
591-
cmd_no_cache, cwd=abs_directory, capture_output=True, text=True
592-
)
593-
sam_build_time = time.time() - sam_build_start
594-
if result.returncode != 0:
595-
# Log detailed error information
596-
error_output = (
597-
f"STDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
598-
)
599-
self.log_error_details(f"SAM build for {directory}", error_output)
600-
return False
562+
# Log detailed error information
563+
error_output = f"STDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
564+
self.log_error_details(f"SAM build for {directory}", error_output)
565+
return False
601566

602567
# Package the template (using absolute paths)
603568
build_template_path = os.path.join(
@@ -633,6 +598,10 @@ def build_and_package_template(self, directory, force_rebuild=False):
633598
self.log_error_details(f"SAM package for {directory}", error_output)
634599
return False
635600

601+
# Log S3 upload location
602+
template_url = f"https://s3.{self.region}.amazonaws.com/{self.bucket}/{self.prefix}/{directory}/template.yaml"
603+
self.console.print(f"[dim] 📤 Template uploaded: {template_url}[/dim]")
604+
636605
# Log timing information
637606
total_time = time.time() - build_start
638607
pattern_name = os.path.basename(directory)
@@ -1139,7 +1108,9 @@ def _scan_lambda_directory(self, src_dir, template_path, context):
11391108
function_key = f"{context}/{func_dir.name}"
11401109
functions[function_key] = {
11411110
"template_path": template_path,
1142-
"function_name": self._extract_function_name(func_dir.name),
1111+
"function_name": self._extract_function_name(
1112+
func_dir.name, template_path
1113+
),
11431114
"source_path": func_dir,
11441115
"context": context,
11451116
"template_dir": template_path.parent,
@@ -1171,34 +1142,66 @@ def _check_requirements_has_idp_common_pkg(self, func_dir):
11711142
except Exception as e:
11721143
return False, f"Error reading requirements.txt: {e}"
11731144

1174-
def _extract_function_name(self, dir_name):
1175-
"""Extract CloudFormation function name from directory name."""
1176-
name_mappings = {
1177-
# Pattern functions
1178-
"bda_invoke_function": "InvokeBDAFunction",
1179-
"bda_completion_function": "BDACompletionFunction",
1180-
"processresults_function": "ProcessResultsFunction",
1181-
"summarization_function": "SummarizationFunction",
1182-
"hitl-process-function": "HITLProcessLambdaFunction",
1183-
"hitl-wait-function": "HITLWaitFunction",
1184-
"hitl-status-update-function": "HITLStatusUpdateFunction",
1185-
"ocr_function": "OCRFunction",
1186-
"classification_function": "ClassificationFunction",
1187-
"extraction_function": "ExtractionFunction",
1188-
"assessment_function": "AssessmentFunction",
1189-
# Main template functions
1190-
"queue_processor": "QueueProcessor",
1191-
"workflow_tracker": "WorkflowTracker",
1192-
"evaluation_function": "EvaluationFunction",
1193-
"save_reporting_data": "SaveReportingDataFunction",
1194-
"queue_sender": "QueueSender",
1195-
"copy_to_baseline_resolver": "CopyToBaselineResolverFunction",
1196-
# Agent-related functions
1197-
"agent_processor": "AgentProcessorFunction",
1198-
"list_available_agents": "ListAvailableAgentsFunction",
1199-
"agent_request_handler": "AgentRequestHandlerFunction",
1200-
}
1201-
return name_mappings.get(dir_name, dir_name)
1145+
def _extract_function_name(self, dir_name, template_path):
1146+
"""Extract CloudFormation function name from template by matching CodeUri."""
1147+
try:
1148+
import yaml
1149+
1150+
# Create a custom loader that ignores CloudFormation intrinsic functions
1151+
class CFLoader(yaml.SafeLoader):
1152+
pass
1153+
1154+
def construct_unknown(loader, node):
1155+
return None
1156+
1157+
# Add constructors for CloudFormation intrinsic functions
1158+
cf_functions = [
1159+
"!Ref",
1160+
"!GetAtt",
1161+
"!Join",
1162+
"!Sub",
1163+
"!Select",
1164+
"!Split",
1165+
"!Base64",
1166+
"!GetAZs",
1167+
"!ImportValue",
1168+
"!FindInMap",
1169+
"!Equals",
1170+
"!And",
1171+
"!Or",
1172+
"!Not",
1173+
"!If",
1174+
"!Condition",
1175+
]
1176+
1177+
for func in cf_functions:
1178+
CFLoader.add_constructor(func, construct_unknown)
1179+
1180+
with open(template_path, "r") as f:
1181+
template = yaml.load(f, Loader=CFLoader)
1182+
1183+
resources = template.get("Resources", {})
1184+
for resource_name, resource_config in resources.items():
1185+
if (
1186+
resource_config
1187+
and resource_config.get("Type") == "AWS::Serverless::Function"
1188+
):
1189+
properties = resource_config.get("Properties", {})
1190+
if properties:
1191+
code_uri = properties.get("CodeUri", "")
1192+
if isinstance(code_uri, str):
1193+
code_uri = code_uri.rstrip("/")
1194+
code_dir = (
1195+
code_uri.split("/")[-1] if "/" in code_uri else code_uri
1196+
)
1197+
if code_dir == dir_name:
1198+
return resource_name
1199+
1200+
return dir_name
1201+
1202+
except Exception as e:
1203+
self.log_verbose(f"Error reading template {template_path}: {e}")
1204+
return dir_name
12021205

12031206
def _validate_idp_common_in_build(self, template_dir, function_name, source_path):
12041207
"""Validate that idp_common package exists in the built Lambda function."""
@@ -1222,24 +1225,6 @@ def _validate_idp_common_in_build(self, template_dir, function_name, source_path
12221225
if not file_path.exists():
12231226
issues.append(f"Missing core file: {core_file}")
12241227

1225-
# Check for key modules based on function type
1226-
module_checks = {
1227-
"InvokeBDAFunction": ["bda/bda_service.py", "bda/__init__.py"],
1228-
"BDACompletionFunction": ["metrics/__init__.py"],
1229-
"ProcessResultsFunction": ["ocr/service.py", "extraction/service.py"],
1230-
"ClassificationFunction": ["classification/service.py"],
1231-
"ExtractionFunction": ["extraction/service.py"],
1232-
"OCRFunction": ["ocr/service.py"],
1233-
"AssessmentFunction": ["assessment/service.py"],
1234-
"SummarizationFunction": ["summarization/service.py"],
1235-
}
1236-
1237-
if function_name in module_checks:
1238-
for module_path in module_checks[function_name]:
1239-
module_file = idp_common_dir / module_path
1240-
if not module_file.exists():
1241-
issues.append(f"Missing function-specific module: {module_path}")
1242-
12431228
return len(issues) == 0, issues
12441229

12451230
def _test_import_functionality(self, template_dir, function_name):
@@ -1333,6 +1318,9 @@ def package_ui(self):
13331318
ui_hash = self.compute_ui_hash()
13341319
zipfile_name = f"src-{ui_hash[:16]}.zip"
13351320

1321+
# Ensure .aws-sam directory exists
1322+
os.makedirs(".aws-sam", exist_ok=True)
1323+
13361324
# Check if we need to rebuild
13371325
existing_zipfiles = [
13381326
f
@@ -1506,35 +1494,70 @@ def build_main_template(self, webui_zipfile, components_needing_rebuild):
15061494
else:
15071495
self.console.print("[green]✅ Main template is up to date[/green]")
15081496

1509-
# Always upload the final template to S3, regardless of whether rebuild was needed
1497+
# Upload main template based on whether build was required
15101498
final_template_key = f"{self.prefix}/{self.main_template}"
15111499
packaged_template_path = ".aws-sam/idp-main.yaml"
15121500

1513-
# Check if packaged template exists, if not we have a problem
1514-
if not os.path.exists(packaged_template_path):
1515-
self.console.print(
1516-
f"[red]Error: Packaged template not found at {packaged_template_path}[/red]"
1517-
)
1518-
self.console.print(
1519-
"[red]This suggests the template was never built. Try running without cache.[/red]"
1520-
)
1521-
sys.exit(1)
1501+
if main_needs_build:
1502+
# Main was rebuilt, so upload the new template
1503+
if not os.path.exists(packaged_template_path):
1504+
self.console.print(
1505+
f"[red]Error: Packaged template not found at {packaged_template_path}[/red]"
1506+
)
1507+
sys.exit(1)
15221508

1523-
self.console.print(
1524-
f"[cyan]Uploading main template to S3: {final_template_key}[/cyan]"
1525-
)
1526-
self.log_verbose(f"Local template path: {packaged_template_path}")
1527-
try:
1528-
self.s3_client.upload_file(
1529-
packaged_template_path,
1530-
self.bucket,
1531-
final_template_key,
1532-
ExtraArgs={"ACL": self.acl},
1509+
self.console.print(
1510+
f"[cyan]Uploading main template to S3: {final_template_key}[/cyan]"
15331511
)
1534-
self.console.print("[green]✅ Main template uploaded successfully[/green]")
1535-
except ClientError as e:
1536-
self.console.print(f"[red]Error uploading main template: {e}[/red]")
1537-
sys.exit(1)
1512+
try:
1513+
self.s3_client.upload_file(
1514+
packaged_template_path,
1515+
self.bucket,
1516+
final_template_key,
1517+
ExtraArgs={"ACL": self.acl},
1518+
)
1519+
self.console.print(
1520+
"[green]✅ Main template uploaded successfully[/green]"
1521+
)
1522+
except Exception as e:
1523+
self.console.print(f"[red]Failed to upload main template: {e}[/red]")
1524+
sys.exit(1)
1525+
else:
1526+
# Main was not rebuilt, check if template exists in S3
1527+
try:
1528+
self.s3_client.head_object(Bucket=self.bucket, Key=final_template_key)
1529+
self.console.print(
1530+
"[green]✅ Main template already exists in S3[/green]"
1531+
)
1532+
except self.s3_client.exceptions.NoSuchKey:
1533+
self.console.print(
1534+
f"[yellow]Main template missing from S3, uploading: {final_template_key}[/yellow]"
1535+
)
1536+
if not os.path.exists(packaged_template_path):
1537+
self.console.print(
1538+
f"[red]Error: No packaged template to upload at {packaged_template_path}[/red]"
1539+
)
1540+
sys.exit(1)
1541+
try:
1542+
self.s3_client.upload_file(
1543+
packaged_template_path,
1544+
self.bucket,
1545+
final_template_key,
1546+
ExtraArgs={"ACL": self.acl},
1547+
)
1548+
self.console.print(
1549+
"[green]✅ Main template uploaded successfully[/green]"
1550+
)
1551+
except Exception as e:
1552+
self.console.print(
1553+
f"[red]Failed to upload main template: {e}[/red]"
1554+
)
1555+
sys.exit(1)
1556+
except Exception as e:
1557+
self.console.print(
1558+
f"[yellow]Could not check S3 template existence: {e}[/yellow]"
1559+
)
1560+
self.console.print("[yellow]Proceeding without upload[/yellow]")
15381561

15391562
# Validate the template
15401563
template_url = (
@@ -1768,10 +1791,11 @@ def get_components_needing_rebuild(self):
17681791
}
17691792
)
17701793

1771-
if self.verbose:
1772-
self.console.print(
1773-
f"[yellow]📝 {component} needs rebuild due to changes in: {', '.join(deps)}[/yellow]"
1774-
)
1794+
self.console.print(
1795+
f"[yellow]📝 {component} needs rebuild due to changes in any of these dependencies:[/yellow]"
1796+
)
1797+
for dep in deps:
1798+
self.console.print(f"[yellow] • {dep}[/yellow]")
17751799

17761800
return components_to_rebuild
17771801

@@ -1783,16 +1807,8 @@ def clear_component_cache(self, component):
17831807
sam_dir = os.path.join(component, ".aws-sam")
17841808

17851809
if os.path.exists(sam_dir):
1786-
build_dir = os.path.join(sam_dir, "build")
1787-
cache_dir = os.path.join(sam_dir, "cache")
1788-
1789-
if os.path.exists(build_dir):
1790-
self.log_verbose(f"Clearing build cache for {component}: {build_dir}")
1791-
shutil.rmtree(build_dir)
1792-
1793-
if os.path.exists(cache_dir):
1794-
self.log_verbose(f"Clearing SAM cache for {component}: {cache_dir}")
1795-
shutil.rmtree(cache_dir)
1810+
self.log_verbose(f"Clearing entire SAM cache for {component}: {sam_dir}")
1811+
shutil.rmtree(sam_dir)
17961812

17971813
def update_component_checksum(self, components_needing_rebuild):
17981814
"""Update checksum for a successfully built component"""
@@ -1918,7 +1934,27 @@ def run(self, args):
19181934
# Perform smart rebuild detection and cache management
19191935
components_needing_rebuild = self.smart_rebuild_detection()
19201936

1921-
# Clear caches for components that need rebuilding
1937+
# Build lib package if it changed
1938+
lib_changed = any(
1939+
comp["component"] == "lib" for comp in components_needing_rebuild
1940+
)
1941+
if lib_changed:
1942+
self.console.print("[bold yellow]📚 Building lib package[/bold yellow]")
1943+
lib_dir = "lib/idp_common_pkg"
1944+
result = subprocess.run(
1945+
["python", "setup.py", "build"],
1946+
cwd=lib_dir,
1947+
capture_output=True,
1948+
text=True,
1949+
)
1950+
if result.returncode != 0:
1951+
self.console.print(
1952+
f"[red]❌ Failed to build lib package: {result.stderr}[/red]"
1953+
)
1954+
sys.exit(1)
1955+
self.console.print("[green]✅ Lib package built successfully[/green]")
1956+
1957+
# clear component cache
19221958
for comp_info in components_needing_rebuild:
19231959
if comp_info["component"] != "lib": # lib doesnt have sam build
19241960
self.clear_component_cache(comp_info["component"])

0 commit comments

Comments
 (0)