Skip to content

Commit 13b4bcb

Browse files
Merge pull request #11 from ahmed-n-abdeltwab/feature/make-modular
fix: artifact packaging and upload in CI workflow
2 parents 6927594 + e97b144 commit 13b4bcb

File tree

3 files changed

+82
-35
lines changed

3 files changed

+82
-35
lines changed

.github/workflows/train_and_release.yml

+39-6
Original file line numberDiff line numberDiff line change
@@ -56,25 +56,55 @@ jobs:
5656
5757
- name: Verify artifacts
5858
run: |
59-
echo "Generated artifacts:"
60-
ls -lR ./$RELEASE_DIR
61-
if [ ! -f ./$RELEASE_DIR/latest/model.pkl ]; then
62-
echo "Error: model.pkl not found!"
59+
echo "Generated artifacts in release/latest:"
60+
ls -lR ./$RELEASE_DIR/latest
61+
62+
REQUIRED_FILES=(
63+
"model.pkl"
64+
"metadata.json"
65+
"metrics.json"
66+
"feature_structure.json"
67+
)
68+
69+
missing_files=0
70+
for file in "${REQUIRED_FILES[@]}"; do
71+
if [ ! -f "./$RELEASE_DIR/latest/$file" ]; then
72+
echo "Error: Required file $file not found!"
73+
missing_files=$((missing_files+1))
74+
fi
75+
done
76+
77+
if [ $missing_files -gt 0 ]; then
78+
echo "Error: Missing $missing_files required files!"
6379
docker logs $(docker ps -lq) || true
6480
exit 1
6581
fi
6682
6783
- name: Create release package
6884
run: |
85+
echo "Contents of release directory before packaging:"
86+
ls -lR ./$RELEASE_DIR
87+
6988
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
7089
RELEASE_NAME="model_$TIMESTAMP"
71-
tar -czvf ./$RELEASE_DIR/$RELEASE_NAME.tar.gz -C ./$RELEASE_DIR/latest .
90+
91+
mkdir -p ./$RELEASE_DIR/temp_package
92+
cp -r ./$RELEASE_DIR/latest/* ./$RELEASE_DIR/temp_package/
93+
94+
tar -czvf ./$RELEASE_DIR/$RELEASE_NAME.tar.gz -C ./$RELEASE_DIR/temp_package .
95+
96+
rm -rf ./$RELEASE_DIR/temp_package
97+
98+
echo "Created package:"
99+
ls -l ./$RELEASE_DIR/*.tar.gz
72100
73101
- name: Upload artifact
102+
if: success()
74103
uses: actions/upload-artifact@v4
75104
with:
76105
name: model-release
77-
path: ./$RELEASE_DIR/*.tar.gz
106+
path: ${{ env.RELEASE_DIR }}/*.tar.gz
107+
if-no-files-found: error
78108

79109
- name: Create GitHub Release
80110
if: startsWith(github.ref, 'refs/tags/')
@@ -85,6 +115,9 @@ jobs:
85115
Automated model training results:
86116
- Model: $(jq -r '.model_type' ./$RELEASE_DIR/latest/metadata.json)
87117
- Timestamp: $(jq -r '.timestamp' ./$RELEASE_DIR/latest/metadata.json)
118+
- Metrics:
119+
Accuracy: $(jq -r '.metrics.accuracy' ./$RELEASE_DIR/latest/metrics.json)
120+
F1 Score: $(jq -r '.metrics.f1' ./$RELEASE_DIR/latest/metrics.json)
88121
files: ./$RELEASE_DIR/*.tar.gz
89122
env:
90123
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

src/components/artifact_exporter.py

+27-18
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,28 @@ class ArtifactExporter:
1111
def __init__(self, config: Dict[str, Any]):
1212
self.config = config
1313
self.config["output_dir"] = os.path.abspath(config.get("output_dir", "release"))
14+
self._ensure_output_directory()
1415

15-
# Ensure directory exists with write permissions
16-
os.makedirs(os.path.join(self.config["output_dir"], "latest"), exist_ok=True)
17-
os.chmod(os.path.join(self.config["output_dir"], "latest"), 0o777)
16+
def _ensure_output_directory(self):
17+
"""Ensure output directory exists with proper permissions"""
18+
release_dir = os.path.join(self.config["output_dir"], "latest")
19+
os.makedirs(release_dir, exist_ok=True)
20+
os.chmod(release_dir, 0o777)
21+
return release_dir
1822

1923
def export_training_artifacts(
2024
self, training_results: Dict[str, Any]
2125
) -> Dict[str, str]:
2226
"""Export all artifacts to release/latest directory"""
23-
try:
24-
release_dir = os.path.join(self.config["output_dir"], "latest")
27+
release_dir = self._ensure_output_directory()
28+
exported_files = {}
2529

30+
try:
2631
# Clear existing contents
2732
shutil.rmtree(release_dir, ignore_errors=True)
2833
os.makedirs(release_dir, exist_ok=True)
2934
os.chmod(release_dir, 0o777)
3035

31-
exported_files = {}
3236
artifacts = training_results["artifacts"]
3337

3438
# Export model files
@@ -42,29 +46,35 @@ def export_training_artifacts(
4246
}
4347

4448
for filename, src in model_files.items():
49+
dest = os.path.join(release_dir, filename)
4550
if src and os.path.exists(src):
46-
dest = os.path.join(release_dir, filename)
4751
shutil.copy2(src, dest)
48-
os.chmod(dest, 0o666) # Ensure writable
52+
os.chmod(dest, 0o644)
4953
exported_files[filename.split(".")[0]] = dest
5054
elif filename == "metrics.json":
51-
# Create default metrics if missing
5255
self._create_default_metrics(release_dir, training_results)
5356
exported_files["metrics"] = os.path.join(release_dir, filename)
5457

5558
# Create supporting files
5659
self._create_feature_structure(release_dir, training_results)
5760
self._create_package_info(release_dir, exported_files)
5861

59-
return {k: os.path.abspath(v) for k, v in exported_files.items()}
62+
# Verify all files were created
63+
for path in exported_files.values():
64+
if not os.path.exists(path):
65+
raise FileNotFoundError(f"Failed to create artifact at {path}")
66+
67+
return exported_files
6068

6169
except Exception as e:
62-
print(f"Artifact export failed: {str(e)}")
63-
raise
70+
# Clean up partial exports
71+
shutil.rmtree(release_dir, ignore_errors=True)
72+
raise RuntimeError(f"Failed to export artifacts: {str(e)}")
6473

6574
def _create_default_metrics(
6675
self, release_dir: str, training_results: Dict[str, Any]
6776
):
77+
metrics_path = os.path.join(release_dir, "metrics.json")
6878
default_metrics = training_results.get(
6979
"metrics",
7080
{
@@ -75,31 +85,30 @@ def _create_default_metrics(
7585
"warning": "Metrics not properly saved during training",
7686
},
7787
)
78-
metrics_path = os.path.join(release_dir, "metrics.json")
7988
with open(metrics_path, "w") as f:
8089
json.dump(default_metrics, f, indent=2)
81-
os.chmod(metrics_path, 0o666)
90+
os.chmod(metrics_path, 0o644)
8291

8392
def _create_feature_structure(
8493
self, release_dir: str, training_results: Dict[str, Any]
8594
):
95+
features_path = os.path.join(release_dir, "feature_structure.json")
8696
feature_structure = {
8797
"feature_names": training_results.get("selected_features", []),
8898
"required_features": len(training_results.get("selected_features", [])),
8999
"version": datetime.now().strftime("%Y%m%d_%H%M%S"),
90100
}
91-
features_path = os.path.join(release_dir, "feature_structure.json")
92101
with open(features_path, "w") as f:
93102
json.dump(feature_structure, f, indent=2)
94-
os.chmod(features_path, 0o666)
103+
os.chmod(features_path, 0o644)
95104

96105
def _create_package_info(self, release_dir: str, files: Dict[str, str]):
106+
info_path = os.path.join(release_dir, "package_info.json")
97107
package_info = {
98108
"created_at": datetime.now().isoformat(),
99109
"contents": {k: os.path.basename(v) for k, v in files.items()},
100110
"notes": "Automatically generated by spyware-detector-training pipeline",
101111
}
102-
info_path = os.path.join(release_dir, "package_info.json")
103112
with open(info_path, "w") as f:
104113
json.dump(package_info, f, indent=2)
105-
os.chmod(info_path, 0o666)
114+
os.chmod(info_path, 0o644)

src/main.py

+16-11
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,30 @@ def main():
3838
pipeline = TrainingPipeline("config/pipeline.yaml")
3939
results = pipeline.run()
4040

41-
# Log results
42-
logger.info("✅ Training completed successfully")
43-
logger.info(f"📊 Model Metrics:\n{json.dumps(results['metrics'], indent=2)}")
44-
45-
# Create compressed release package
46-
shutil.make_archive("model_release", "zip", release_dir)
47-
logger.info("📦 Created release package: model_release.zip")
48-
49-
# Verify all artifacts were created
41+
# Verify exported files exist
42+
logger.info("Verifying exported files:")
5043
missing_files = []
5144
for name, path in results["exported_files"].items():
5245
if path and os.path.exists(path):
5346
logger.info(f" - {name}: {path}")
47+
os.chmod(path, 0o644)
5448
else:
49+
logger.error(f" - {name}: FILE MISSING")
5550
missing_files.append(name)
56-
logger.warning(f" - {name}: FILE MISSING")
5751

5852
if missing_files:
59-
raise RuntimeError(f"Missing exported files: {', '.join(missing_files)}")
53+
raise FileNotFoundError(
54+
f"Missing exported files: {', '.join(missing_files)}"
55+
)
56+
57+
# Log results
58+
logger.info("✅ Training completed successfully")
59+
logger.info(f"📊 Model Metrics:\n{json.dumps(results['metrics'], indent=2)}")
60+
61+
# Create compressed release package
62+
archive_path = os.path.join(release_dir, "model_release.zip")
63+
shutil.make_archive(archive_path[:-4], "zip", release_dir)
64+
logger.info(f"📦 Created release package: {archive_path}")
6065

6166
except Exception as e:
6267
logger.error(f"❌ Pipeline failed: {str(e)}", exc_info=True)

0 commit comments

Comments
 (0)