Skip to content
Open
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
27 changes: 27 additions & 0 deletions .github/workflows/check-in-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
- target: check-jira-issue-fetcher-in-container
- target: check-ymir-common-in-container
- target: check-supervisor-in-container
- target: check-sweep-in-container
- target: check-mcp-install-in-container
steps:
- name: Checkout code
Expand Down Expand Up @@ -63,3 +64,29 @@ jobs:
- name: Test unprivileged gateway module
run: |
podman run --rm mcp-gateway-test:pr python3.13 -c "from ymir.tools.unprivileged.gateway import main; print('✓ unprivileged gateway import successful')"

smoke-test-sweep-containerfile:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Set up Podman
run: |
sudo apt-get update
sudo apt-get install -y podman
podman --version
- name: Build sweep container
run: |
podman build --no-cache -f Containerfile.sweep -t sweep-test:pr .
# Containerfile.sweep copies only a curated subset of ymir/* (sweep, common,
# supervisor, tools) onto PYTHONPATH rather than pip-installing the package.
# The slim image's import graph is therefore an unenforced contract: if a
# package-structure change makes the sweep's imports reach outside that
# subset (or pull in a dep this Containerfile doesn't install), the image
# breaks only at runtime. These imports turn that into a build-time failure.
- name: Test sweep strategy imports
run: |
podman run --rm sweep-test:pr python3 -c "from ymir.sweep.y_stream import YStreamSweep; from ymir.tools.privileged.jira import CheckCveTriageEligibilityTool; print('✓ sweep strategy + eligibility tool import successful')"
- name: Test sweep CLI entrypoint
run: |
podman run --rm sweep-test:pr python3 -m ymir.sweep --help
57 changes: 57 additions & 0 deletions Containerfile.sweep
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
FROM quay.io/centos/centos:stream10
Comment thread
jpodivin marked this conversation as resolved.

RUN dnf -y install epel-release \
&& dnf config-manager --set-enabled crb \
&& dnf -y update

RUN dnf -y install --allowerasing \
# Build dependencies (removed after pip install)
gcc \
gcc-c++ \
python3-devel \
# Runtime system packages
python3 \
python3-backoff \
python3-koji \
python3-pip \
python3-requests
RUN pip3 install -v --no-cache-dir \
"litellm!=1.82.7,!=1.82.8" \
"beeai-framework==0.1.82" \
"mcp<1.29.0" \
pydantic \
aiofiles \
aiohttp \
httpx \
jinja2 \
redis \
specfile \
&& dnf -y remove gcc gcc-c++ python3-devel \
&& dnf clean all

# Create unprivileged user
RUN useradd -m -G wheel sweepbot

WORKDIR /home/sweepbot

# Copy only the modules required by the sweep.
# ymir.sweep — the sweep module itself
# ymir.common — shared utilities, models, constants, config
# ymir.supervisor — jira_utils, gitlab_utils, http_utils, supervisor_types
# ymir.tools — tools constants (YMIR_USER_AGENT, imported by http_utils) and
# ymir.tools.privileged.jira (CheckCveTriageEligibilityTool, which
# the y_stream sweep re-runs to decide unblocking)
COPY ymir/sweep/ /home/sweepbot/ymir/sweep/
COPY ymir/common/ /home/sweepbot/ymir/common/
COPY ymir/supervisor/ /home/sweepbot/ymir/supervisor/
COPY ymir/tools/ /home/sweepbot/ymir/tools/

RUN chgrp -R root /home/sweepbot && chmod -R g+rwX /home/sweepbot

USER sweepbot
ENV HOME=/home/sweepbot
WORKDIR $HOME

ENV PYTHONPATH=$HOME:$PYTHONPATH

CMD ["python3", "-m", "ymir.sweep", "--all"]
24 changes: 23 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,24 @@ build-jira-issue-fetcher:
build-mr-cleanup:
$(COMPOSE) --profile manual build mr-cleanup

.PHONY: build-sweep
build-sweep:
$(CONTAINER_TOOL) build -f Containerfile.sweep -t sweep:latest .

.PHONY: run-sweep
run-sweep:
@if [ -z "$(STRATEGY)" ]; then \
echo "Usage: make run-sweep STRATEGY=dependency|y_stream|pr_pending|no_patch"; \
exit 1; \
fi
@if [ ! -f .secrets/sweep.env ]; then \
echo "Error: .secrets/sweep.env not found"; \
echo "Copy the template: cp templates/sweep.env .secrets/sweep.env"; \
echo "Then edit it with your credentials"; \
exit 1; \
fi
$(COMPOSE) -f $(COMPOSE_FILE) --profile manual run --rm sweep python3 -m ymir.sweep --strategy $(STRATEGY)

# Usage:
# make run-mr-cleanup-dry-run # dry run, lists what would be changed
# make run-mr-cleanup # live run
Expand Down Expand Up @@ -462,7 +480,7 @@ redis-cli:
build-test-image:
$(MAKE) -f Makefile.tests build-test-image

.PHONY: check-in-container check-agents-in-container check-unprivileged-tools-in-container check-privileged-tools-in-container check-jira-issue-fetcher-in-container check-ymir-common-in-container check-supervisor-in-container check-mcp-install-in-container check-cli-in-container
.PHONY: check-in-container check-agents-in-container check-unprivileged-tools-in-container check-privileged-tools-in-container check-jira-issue-fetcher-in-container check-ymir-common-in-container check-supervisor-in-container check-mcp-install-in-container check-cli-in-container check-sweep-in-container check-sweep-integration-in-container
check-in-container: build-test-image
$(MAKE) -f Makefile.tests check-in-container
check-agents-in-container: build-test-image
Expand All @@ -477,6 +495,10 @@ check-ymir-common-in-container: build-test-image
$(MAKE) -f Makefile.tests check-ymir-common-in-container
check-supervisor-in-container: build-test-image
$(MAKE) -f Makefile.tests check-supervisor-in-container
check-sweep-in-container: build-test-image
$(MAKE) -f Makefile.tests check-sweep-in-container
check-sweep-integration-in-container: build-test-image
$(MAKE) -f Makefile.tests check-sweep-integration-in-container
check-mcp-install-in-container: build-test-image
$(MAKE) -f Makefile.tests check-mcp-install-in-container
check-cli-in-container: build-test-image
Expand Down
17 changes: 14 additions & 3 deletions Makefile.tests
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ build-test-image-c9s:
$(CONTAINER_ENGINE) build --rm --tag $(TEST_IMAGE_C9S) -f Containerfile.c9s-tests

.PHONY: check check-agents check-cli check-unprivileged-tools check-privileged-tools check-jira-issue-fetcher check-ymir-common \
check-supervisor check-mcp-install check-in-container check-agents-in-container check-cli-in-container \
check-supervisor check-mcp-install check-sweep check-sweep-integration \
check-in-container check-agents-in-container check-cli-in-container \
check-unprivileged-tools-in-container \
check-privileged-tools-in-container check-jira-issue-fetcher-in-container check-ymir-common-in-container \
check-supervisor-in-container check-mcp-install-in-container
check-supervisor-in-container check-mcp-install-in-container check-sweep-in-container check-sweep-integration-in-container

define RUN_TESTS
PYTHONPATH=$(CURDIR) PYTHONDONTWRITEBYTECODE=1 python3 -m pytest --verbose --showlocals $(addprefix $(1),$(TEST_TARGET))
Expand All @@ -39,11 +40,16 @@ check-ymir-common:
$(call RUN_TESTS,ymir/common/)
check-supervisor:
$(call RUN_TESTS,ymir/supervisor/)
check-sweep:
$(call RUN_TESTS,ymir/sweep/)
check-sweep-integration: TEST_TARGET = ./tests/integration
check-sweep-integration:
$(call RUN_TESTS,ymir/sweep/)

check-mcp-install:
PYTHONPATH= bash scripts/test_mcp_install.sh

check: check-agents check-cli check-unprivileged-tools check-privileged-tools check-jira-issue-fetcher check-ymir-common check-supervisor check-mcp-install
check: check-agents check-cli check-unprivileged-tools check-privileged-tools check-jira-issue-fetcher check-ymir-common check-supervisor check-sweep check-mcp-install

define RUN_TESTS_IN_CONTAINER
$(CONTAINER_ENGINE) run --rm -it -v $(CURDIR):/src:z --env TEST_TARGET $(1) make -f Makefile.tests $(2)
Expand All @@ -63,10 +69,15 @@ check-ymir-common-in-container:
$(call RUN_TESTS_IN_CONTAINER,$(TEST_IMAGE_C9S),check-ymir-common)
check-supervisor-in-container:
$(call RUN_TESTS_IN_CONTAINER,$(TEST_IMAGE_C9S),check-supervisor)
check-sweep-in-container:
$(call RUN_TESTS_IN_CONTAINER,$(TEST_IMAGE),check-sweep)
check-sweep-integration-in-container:
$(call RUN_TESTS_IN_CONTAINER,$(TEST_IMAGE),check-sweep-integration)
check-mcp-install-in-container:
$(call RUN_TESTS_IN_CONTAINER,$(TEST_IMAGE),check-mcp-install)

check-in-container: check-agents-in-container check-cli-in-container check-unprivileged-tools-in-container \
check-privileged-tools-in-container \
check-jira-issue-fetcher-in-container check-ymir-common-in-container check-supervisor-in-container \
check-sweep-in-container \
check-mcp-install-in-container
12 changes: 12 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,18 @@ services:
restart: "no"
profiles: ["manual"]

sweep:
image: sweep
build:
context: .
dockerfile: Containerfile.sweep
environment:
- REDIS_URL=redis://valkey:6379/0
env_file:
- .secrets/sweep.env
restart: "no"
profiles: ["manual"]


volumes:
valkey-data:
Expand Down
32 changes: 31 additions & 1 deletion openshift/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,34 @@ print(json.dumps({ \
oc exec deployment/valkey -- valkey-cli LPUSH $$queue "$$payload" && \
echo "✓ Pushed to $$queue. View with: make show-reproducer-queue"

run-sweep-dependency:
oc delete job sweep-dependency-manual --ignore-not-found
oc create job sweep-dependency-manual --from=cronjob/sweep-dependency

run-sweep-y-stream:
oc delete job sweep-y-stream-manual --ignore-not-found
oc create job sweep-y-stream-manual --from=cronjob/sweep-y-stream

run-sweep-pr-pending:
oc delete job sweep-pr-pending-manual --ignore-not-found
oc create job sweep-pr-pending-manual --from=cronjob/sweep-pr-pending

run-sweep-no-patch:
oc delete job sweep-no-patch-manual --ignore-not-found
oc create job sweep-no-patch-manual --from=cronjob/sweep-no-patch

suspend-sweeps:
oc patch cronjob sweep-dependency --type merge -p '{"spec":{"suspend":true}}'
oc patch cronjob sweep-y-stream --type merge -p '{"spec":{"suspend":true}}'
oc patch cronjob sweep-pr-pending --type merge -p '{"spec":{"suspend":true}}'
oc patch cronjob sweep-no-patch --type merge -p '{"spec":{"suspend":true}}'

unsuspend-sweeps:
oc patch cronjob sweep-dependency --type merge -p '{"spec":{"suspend":false}}'
oc patch cronjob sweep-y-stream --type merge -p '{"spec":{"suspend":false}}'
oc patch cronjob sweep-pr-pending --type merge -p '{"spec":{"suspend":false}}'
oc patch cronjob sweep-no-patch --type merge -p '{"spec":{"suspend":false}}'

.PHONY: deploy run-jira-issue-fetcher run-jira-issue-fetcher-todo \
suspend-jira-issue-fetcher unsuspend-jira-issue-fetcher \
suspend-jira-issue-fetcher-todo unsuspend-jira-issue-fetcher-todo \
Expand All @@ -180,4 +208,6 @@ print(json.dumps({ \
logs-triage logs-backport-c9s logs-backport-c10s \
logs-rebase-c9s logs-rebase-c10s logs-rebuild-c9s logs-rebuild-c10s \
logs-reproducer logs-mcp logs-supervisor logs-valkey logs-phoenix \
logs-redis-commander logs-otel-collector process trigger-reproducer
logs-redis-commander logs-otel-collector process trigger-reproducer \
run-sweep-dependency run-sweep-y-stream run-sweep-pr-pending run-sweep-no-patch \
suspend-sweeps unsuspend-sweeps
59 changes: 59 additions & 0 deletions openshift/cronjob-sweep-dependency.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: sweep-dependency
labels:
app: sweep-dependency
component: scheduler
spec:
schedule: "0 */6 * * *" # Every 6 hours
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
suspend: false
jobTemplate:
metadata:
labels:
app: sweep-dependency
component: job
spec:
backoffLimit: 2
activeDeadlineSeconds: 600 # 10 minutes max runtime
template:
metadata:
labels:
app: sweep-dependency
component: pod
spec:
restartPolicy: Never
containers:
- name: sweep-dependency
image: 'sweep:prod'
imagePullPolicy: IfNotPresent
args: ["python3", "-m", "ymir.sweep", "--strategy", "dependency"]
envFrom:
- configMapRef:
name: endpoints-env
- configMapRef:
name: jira-env
- secretRef:
name: jira-env
resources:
limits:
cpu: "200m"
memory: "256Mi"
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
capabilities:
drop:
- ALL
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
dnsPolicy: ClusterFirst
schedulerName: default-scheduler
terminationGracePeriodSeconds: 30
64 changes: 64 additions & 0 deletions openshift/cronjob-sweep-no-patch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: sweep-no-patch
labels:
app: sweep-no-patch
component: scheduler
spec:
schedule: "0 4 * * *" # Daily at 4am UTC
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
suspend: false
jobTemplate:
metadata:
labels:
app: sweep-no-patch
component: job
spec:
backoffLimit: 2
# Shorter deadline: this sweep only pushes to Redis, the expensive
# LLM work happens in the triage agent pods.
activeDeadlineSeconds: 300 # 5 minutes max runtime
template:
metadata:
labels:
app: sweep-no-patch
component: pod
spec:
restartPolicy: Never
containers:
- name: sweep-no-patch
image: 'sweep:prod'
imagePullPolicy: IfNotPresent
args: ["python3", "-m", "ymir.sweep", "--strategy", "no_patch"]
env:
- name: MAX_ISSUES_PER_RUN
value: "20"
envFrom:
- configMapRef:
name: endpoints-env
- configMapRef:
name: jira-env
- secretRef:
name: jira-env
resources:
limits:
cpu: "200m"
memory: "128Mi"
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
capabilities:
drop:
- ALL
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
dnsPolicy: ClusterFirst
schedulerName: default-scheduler
terminationGracePeriodSeconds: 30
Loading
Loading