Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/build-and-push-model-registry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Build and push - model registry image

on:
workflow_dispatch:

env:
PROJECT_DIR: "."

jobs:
build-and-push-image:
name: Build and Push container image
runs-on: ubuntu-latest
strategy:
matrix:
include:
- name: rec-sys-model-registry
context: oc-tools
image-name: rec-sys-model-registry

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Quay.io
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}

- name: Set version from last commit hash
id: version
run: |
echo "tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT

- name: Build and push ${{ matrix.name }}
uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
file: Containerfile
push: true
tags: |
quay.io/rh-ai-kickstart/${{ matrix.image-name }}:${{ steps.version.outputs.tag }}
quay.io/rh-ai-kickstart/${{ matrix.image-name }}:latest
build-args: |
IMAGE_TAG=${{ steps.version.outputs.tag }}
10 changes: 6 additions & 4 deletions .github/workflows/build-and-push.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build and push image
name: Build and push - pipeline image

on:
workflow_dispatch:
Expand Down Expand Up @@ -31,17 +31,19 @@ jobs:
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}

- name: Set version from run number
- name: Set version from last commit hash
id: version
run: |
echo "tag=v1.0.${GITHUB_RUN_NUMBER}" >> $GITHUB_OUTPUT
echo "tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT

- name: Build and push ${{ matrix.name }}
uses: docker/build-push-action@v5
with:
context: ${{ matrix.context }}
file: Containerfile
push: true
tags: quay.io/rh-ai-kickstart/${{ matrix.image-name }}:${{ steps.version.outputs.tag }}
tags: |
quay.io/rh-ai-kickstart/${{ matrix.image-name }}:${{ steps.version.outputs.tag }}
quay.io/rh-ai-kickstart/${{ matrix.image-name }}:latest
build-args: |
IMAGE_TAG=${{ steps.version.outputs.tag }}
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,7 @@ cython_debug/
# PyPI configuration file
.pypirc

*.crt
*.crt

# Generated by train-workflow.py
train-workflow.yaml
12 changes: 12 additions & 0 deletions oc-tools/Containerfile

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you changed the Containerfile location, you will need to update the build path in the CI as well.

@Hadar301 Hadar301 Jul 30, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a change in the Containerfile location, it's a new one, the thing is, that this image does not rely on our code or anything from our repo, we simply need an image that has openshift client with additional properties. |
Do you think we should build it each time the build and push is triggered?

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.11-slim
WORKDIR /app
# Install curl and tar for downloading oc, then install oc CLI
RUN apt-get update && \
apt-get install -y curl tar jq && \
curl -L https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/openshift-client-linux.tar.gz | tar -xz -C /usr/local/bin oc && \
chmod +x /usr/local/bin/oc && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Install Python dependencies
RUN pip install --upgrade pip && \
pip install --no-cache-dir model_registry==0.2.21
Comment thread
Hadar301 marked this conversation as resolved.
63 changes: 63 additions & 0 deletions oc-tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# OpenShift CLI Tools Container

This container image provides OpenShift CLI tools and utilities needed for cluster credential management in the recommendation system workflow.

## Purpose

This specialized image is used by the `fetch_cluster_credentials` component in the Kubeflow pipeline to:
- Authenticate with the OpenShift cluster
- Retrieve user tokens and cluster information
- Get Model Registry service endpoints
- Extract routing information for external services

## Contents

### Tools Included
- **OpenShift CLI (`oc`)** - Latest version from OpenShift mirror
- **jq** - JSON processor for parsing API responses
- **curl** - For downloading the OpenShift CLI
- **tar** - For extracting downloaded archives

### Python Base
- **Python 3.11-slim** - Minimal Python runtime
- **model_registry** - Python package for model registry operations (installed via pip at runtime)

## Usage in Pipeline

This image is used by the `fetch_cluster_credentials()` function in `train-workflow.py`

## Building the Image

```bash
# From the oc-tools/ directory
podman build --platform linux/amd64 -t quay.io/ecosystem-appeng/model-registry .
# Push to registry
podman push quay.io/ecosystem-appeng/model-registry:latest
```

## Why Separate from Base Image?

This image is kept separate from the main `BASE_IMAGE` because:

1. **Security Isolation** - OpenShift CLI tools have cluster access privileges
2. **Image Size** - ML workloads don't need cluster management tools
3. **Separation of Concerns** - Infrastructure operations vs. ML operations
4. **Maintenance** - Can update OC tools independently of ML dependencies

## Environment Variables

The component using this image expects these environment variables:
- `MODEL_REGISTRY_NAMESPACE` - Namespace where model registry is deployed
- `MODEL_REGISTRY_CONTAINER` - Name of the model registry service

## Dependencies

This image requires the pod to have:
- ServiceAccount with cluster read permissions
- Access to the OpenShift API server
- Network connectivity to model registry services

## Related Components

- `registry_model_to_model_registry()` - Consumes the credentials from this component
- `train_model()` - Provides model artifacts to be registered
20 changes: 7 additions & 13 deletions train-workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from kfp.dsl import Artifact, Dataset, Input, Model, Output

BASE_IMAGE = os.getenv(
"BASE_REC_SYS_IMAGE", "quay.io/ecosystem-appeng/rec-sys-app:0.0.43"
"BASE_REC_SYS_IMAGE", "quay.io/rh-ai-kickstart/rec-sys-app:latest"
)


Expand Down Expand Up @@ -250,9 +250,7 @@ def table_exists(engine, table_name):
)
new_version = "1.0.0"
connection.execute(
text(
f"INSERT INTO model_version (version) VALUES " f"('{new_version}');"
)
text(f"INSERT INTO model_version (version) VALUES ('{new_version}');")
)
connection.commit()
else:
Expand Down Expand Up @@ -312,9 +310,9 @@ def table_exists(engine, table_name):
return modelMetadata(bucket_name, new_version, object_name, torch.__version__[0:5])


@dsl.component(base_image="quay.io/rh-ee-ofridman/model-registry-python-oc")
def fetch_cluster_credentials() -> (
NamedTuple("ocContext", [("author", str), ("user_token", str), ("host", str)])
@dsl.component(base_image="quay.io/rh-ai-kickstart/rec-sys-model-registry:latest")
def fetch_cluster_credentials() -> NamedTuple(
"ocContext", [("author", str), ("user_token", str), ("host", str)]
):
import os
import subprocess
Expand Down Expand Up @@ -372,7 +370,7 @@ def registry_model_to_model_registry(
path=object_name,
region=os.environ.get("REGION", "us-east-1"),
),
version=(f"{new_version}_" f"{datetime.now().strftime('%Y_%m_%d_%H_%M_%S')}"),
version=(f"{new_version}_{datetime.now().strftime('%Y_%m_%d_%H_%M_%S')}"),
model_format_name="pytorch",
model_format_version=torch_version,
storage_key="minio",
Expand Down Expand Up @@ -499,15 +497,11 @@ def mount_secret_feast_repository(task):
)
dataset_url = os.getenv("DATASET_URL")
if dataset_url is not None:
task.set_env_variable(
name="DATASET_URL",
value=dataset_url
)
task.set_env_variable(name="DATASET_URL", value=dataset_url)


@dsl.pipeline(name=os.path.basename(__file__).replace(".py", ""))
def batch_recommendation():

load_data_task = load_data_from_feast()
mount_secret_feast_repository(load_data_task)
# Component configurations
Expand Down
Loading