chore: import upstream snapshot with attribution
Sync docs with Docusaurus / sync (push) Waiting to run
Tests / Check if changed (push) Waiting to run
Tests / format (push) Blocked by required conditions
Tests / check-imports (push) Blocked by required conditions
Tests / Unit / macos-latest (push) Blocked by required conditions
Tests / Unit / ubuntu-latest (push) Blocked by required conditions
Tests / Unit / windows-latest (push) Blocked by required conditions
Tests / mypy (push) Blocked by required conditions
Tests / Integration / ubuntu-latest (push) Blocked by required conditions
Tests / Integration / macos-latest (push) Blocked by required conditions
Tests / Integration / windows-latest (push) Blocked by required conditions
Tests / notify-slack-on-failure (push) Blocked by required conditions
Tests / Mark tests as completed (push) Blocked by required conditions
Docker image release / Build base image (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Waiting to run
Tests / Check if changed (push) Waiting to run
Tests / format (push) Blocked by required conditions
Tests / check-imports (push) Blocked by required conditions
Tests / Unit / macos-latest (push) Blocked by required conditions
Tests / Unit / ubuntu-latest (push) Blocked by required conditions
Tests / Unit / windows-latest (push) Blocked by required conditions
Tests / mypy (push) Blocked by required conditions
Tests / Integration / ubuntu-latest (push) Blocked by required conditions
Tests / Integration / macos-latest (push) Blocked by required conditions
Tests / Integration / windows-latest (push) Blocked by required conditions
Tests / notify-slack-on-failure (push) Blocked by required conditions
Tests / Mark tests as completed (push) Blocked by required conditions
Docker image release / Build base image (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# See https://help.github.com/articles/about-codeowners/ for syntax
|
||||
|
||||
# Core Engineering will be the default owners for everything
|
||||
# in the repo. Unless a later match takes precedence,
|
||||
# @deepset-ai/core-engineering will be requested for review
|
||||
# when someone opens a pull request.
|
||||
* @deepset-ai/open-source-engineering
|
||||
|
||||
# Documentation
|
||||
*.md @deepset-ai/documentation @deepset-ai/open-source-engineering
|
||||
|
||||
# Auto-synced API reference (no human reviewers needed, auto-approved by GitHub bot)
|
||||
docs-website/reference/
|
||||
docs-website/reference_versioned_docs/
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Errors you encountered
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
type: Bug
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Error message**
|
||||
Error that was thrown (if available)
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here, like document types / preprocessing steps / settings of reader etc.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior
|
||||
|
||||
**FAQ Check**
|
||||
- [ ] Have you had a look at [our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)?
|
||||
|
||||
**System:**
|
||||
- OS:
|
||||
- Haystack version:
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Something unclear? Just ask :)
|
||||
url: https://github.com/deepset-ai/haystack/discussions/new
|
||||
about: Start a Github discussion with your question
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
type: Feature
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,44 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
cooldown:
|
||||
default-days: 1
|
||||
|
||||
- package-ecosystem: 'pip'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
cooldown:
|
||||
default-days: 1
|
||||
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/docs-website'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
cooldown:
|
||||
default-days: 1
|
||||
|
||||
# Tracks the digest-pinned `COPY --from` image (ghcr.io/astral-sh/uv) in
|
||||
# docker/Dockerfile.base. Note: the python:3.12-slim base/builder digests are
|
||||
# carried both as the build_image/base_image ARG defaults in Dockerfile.base
|
||||
# and in docker/docker-bake.hcl (which overrides them at build time).
|
||||
# Dependabot does NOT parse images that reach FROM via an ARG, nor the
|
||||
# `docker run` image strings in workflows, so bump those by hand and keep the
|
||||
# Dockerfile.base ARG defaults and docker-bake.hcl digests in sync.
|
||||
- package-ecosystem: 'docker'
|
||||
directory: '/docker'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
cooldown:
|
||||
default-days: 1
|
||||
|
||||
# Keeps the digest-pinned OSS-Fuzz base-builder in .clusterfuzzlite/Dockerfile fresh.
|
||||
- package-ecosystem: 'docker'
|
||||
directory: '/.clusterfuzzlite'
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
cooldown:
|
||||
default-days: 1
|
||||
@@ -0,0 +1,37 @@
|
||||
# Release lines
|
||||
1.x:
|
||||
- base-branch: 'v1.x'
|
||||
|
||||
# Proposals
|
||||
proposal:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: proposals/text/*
|
||||
|
||||
# Topics
|
||||
topic:tests:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['test/**/*','test/*']
|
||||
|
||||
topic:docker:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: docker/*
|
||||
|
||||
topic:CI:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['.github/*','.github/**/*']
|
||||
|
||||
topic:DX:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ["CONTRIBUTING.md", ".pre-commit-config.yaml",".gitignore"]
|
||||
|
||||
topic:build/distribution:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: pyproject.toml
|
||||
|
||||
topic:security:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: SECURITY.md
|
||||
|
||||
topic:core:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: haystack/core/**/*
|
||||
@@ -0,0 +1,26 @@
|
||||
### Related Issues
|
||||
|
||||
- fixes #issue-number
|
||||
|
||||
### Proposed Changes:
|
||||
|
||||
<!--- In case of a bug: Describe what caused the issue and how you solved it -->
|
||||
<!--- In case of a feature: Describe what did you add and how it works -->
|
||||
|
||||
### How did you test it?
|
||||
|
||||
<!-- unit tests, integration tests, manual verification, instructions for manual tests -->
|
||||
|
||||
### Notes for the reviewer
|
||||
|
||||
<!-- E.g. point out section where the reviewer -->
|
||||
|
||||
### Checklist
|
||||
|
||||
- I have read the [contributors guidelines](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md) and the [code of conduct](https://github.com/deepset-ai/haystack/blob/main/code_of_conduct.txt).
|
||||
- I have updated the related issue with new insights and changes.
|
||||
- I have added unit tests and updated the docstrings.
|
||||
- I've used one of the [conventional commit types](https://www.conventionalcommits.org/en/v1.0.0/) for my PR title: `fix:`, `feat:`, `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:` and added `!` in case the PR includes breaking changes.
|
||||
- I have documented my code.
|
||||
- I have added a release note file, following the [contributors guidelines](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md#release-notes).
|
||||
- I have run [pre-commit hooks](https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md#installation) and fixed any issue.
|
||||
@@ -0,0 +1,85 @@
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
from haystack import logging # noqa: F401 # this is needed to avoid circular imports
|
||||
|
||||
|
||||
def validate_module_imports(root_dir: str, exclude_subdirs: list[str] | None = None) -> tuple[list, list]:
|
||||
"""
|
||||
Recursively search for all Python modules and attempt to import them.
|
||||
|
||||
This includes both packages (directories with __init__.py) and individual Python files.
|
||||
"""
|
||||
imported = []
|
||||
failed = []
|
||||
exclude_subdirs = (exclude_subdirs or []) + ["__pycache__"]
|
||||
|
||||
# Add the root directory to the Python path
|
||||
sys.path.insert(0, root_dir)
|
||||
base_path = Path(root_dir)
|
||||
|
||||
for root, _, files in os.walk(root_dir):
|
||||
if any(subdir in root for subdir in exclude_subdirs):
|
||||
continue
|
||||
|
||||
# Convert path to module format
|
||||
module_path = ".".join(Path(root).relative_to(base_path.parent).parts)
|
||||
python_files = [f for f in files if f.endswith(".py")]
|
||||
|
||||
# Try importing package and individual files
|
||||
for file in python_files:
|
||||
try:
|
||||
if file == "__init__.py":
|
||||
module_to_import = module_path
|
||||
else:
|
||||
module_name = os.path.splitext(file)[0]
|
||||
module_to_import = f"{module_path}.{module_name}" if module_path else module_name
|
||||
|
||||
importlib.import_module(module_to_import)
|
||||
imported.append(module_to_import)
|
||||
except Exception:
|
||||
failed.append({"module": module_to_import, "traceback": traceback.format_exc()})
|
||||
|
||||
return imported, failed
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
This script checks that all Haystack modules can be imported successfully.
|
||||
|
||||
This includes both packages and individual Python files.
|
||||
This can detect several issues, such as:
|
||||
- Syntax errors in Python files
|
||||
- Missing dependencies
|
||||
- Circular imports
|
||||
- Incorrect type hints without forward references
|
||||
"""
|
||||
# Add any subdirectories you want to skip during import checks ("__pycache__" is skipped by default)
|
||||
exclude_subdirs = ["testing"]
|
||||
|
||||
print("Checking imports from all Haystack modules...")
|
||||
imported, failed = validate_module_imports(root_dir="haystack", exclude_subdirs=exclude_subdirs)
|
||||
|
||||
if not imported:
|
||||
print("\nNO MODULES WERE IMPORTED")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\nSUCCESSFULLY IMPORTED {len(imported)} MODULES")
|
||||
|
||||
if failed:
|
||||
print(f"\nFAILED TO IMPORT {len(failed)} MODULES:")
|
||||
for fail in failed:
|
||||
print(f" - {fail['module']}")
|
||||
|
||||
print("\nERRORS:")
|
||||
for fail in failed:
|
||||
print(f" - {fail['module']}\n")
|
||||
print(f" {fail['traceback']}\n\n")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
This script creates an unstable documentation version at the time of branch-off for a new Haystack release.
|
||||
|
||||
Between branch-off and the actual release, two unstable doc versions coexist.
|
||||
If we branch off for 2.20, we have:
|
||||
1. the target unstable version, 2.20-unstable (lives in docs-website/versioned_docs/version-2.20-unstable)
|
||||
2. the next unstable version, 2.21-unstable (lives in docs-website/docs)
|
||||
|
||||
This script takes care of all the necessary updates to the documentation website.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
VERSION_VALIDATOR = re.compile(r"^[0-9]+\.[0-9]+$")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-v", "--new-version", help="The new unstable version that is being created (e.g. 2.20).", required=True
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if VERSION_VALIDATOR.match(args.new_version) is None:
|
||||
sys.exit("Version must be formatted like so <major>.<minor>")
|
||||
|
||||
target_version = f"{args.new_version}" # e.g., "2.20" - the target release version
|
||||
major, minor = args.new_version.split(".")
|
||||
|
||||
target_unstable = f"{target_version}-unstable" # e.g., "2.20-unstable"
|
||||
next_unstable = f"{major}.{int(minor) + 1}-unstable" # e.g., "2.21-unstable" - next cycle
|
||||
|
||||
versions = [
|
||||
folder.replace("version-", "")
|
||||
for folder in os.listdir("docs-website/versioned_docs")
|
||||
if os.path.isdir(os.path.join("docs-website/versioned_docs", folder))
|
||||
]
|
||||
|
||||
# Check if the versions we're about to create already exist in versioned_docs
|
||||
if target_version in versions:
|
||||
sys.exit(f"{target_version} already exists (already released). Aborting.")
|
||||
if target_unstable in versions:
|
||||
print(f"{target_unstable} already exists. Nothing to do.")
|
||||
sys.exit(0)
|
||||
|
||||
# Create new unstable from the currently existing one.
|
||||
# The new unstable will be made stable at a later time by another workflow
|
||||
print(f"Creating new unstable version {target_unstable} from main")
|
||||
|
||||
### Docusaurus updates
|
||||
|
||||
# copy docs to versioned_docs/version-target_unstable
|
||||
shutil.copytree("docs-website/docs", f"docs-website/versioned_docs/version-{target_unstable}")
|
||||
|
||||
# copy reference to reference_versioned_docs/version-target_unstable
|
||||
shutil.copytree("docs-website/reference", f"docs-website/reference_versioned_docs/version-{target_unstable}")
|
||||
|
||||
# generate versioned_sidebars/version-target_unstable-sidebars.json from the current sidebars.js
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
subprocess.run(
|
||||
["node", "docs-website/scripts/extract_sidebar.mjs", "docs-website/sidebars.js", tmp_path], check=True
|
||||
)
|
||||
docs_sidebar_dest = f"docs-website/versioned_sidebars/version-{target_unstable}-sidebars.json"
|
||||
shutil.move(tmp_path, docs_sidebar_dest)
|
||||
|
||||
# generate reference_versioned_sidebars/version-target_unstable-sidebars.json from the current reference-sidebars.js
|
||||
ref_sidebar_dest = f"docs-website/reference_versioned_sidebars/version-{target_unstable}-sidebars.json"
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
subprocess.run(
|
||||
["node", "docs-website/scripts/extract_sidebar.mjs", "docs-website/reference-sidebars.js", tmp_path], check=True
|
||||
)
|
||||
shutil.move(tmp_path, ref_sidebar_dest)
|
||||
|
||||
# add unstable version to versions.json
|
||||
with open("docs-website/versions.json") as f:
|
||||
versions_list = json.load(f)
|
||||
versions_list.insert(0, target_unstable)
|
||||
with open("docs-website/versions.json", "w") as f:
|
||||
json.dump(versions_list, f)
|
||||
|
||||
# add unstable version to reference_versions.json
|
||||
with open("docs-website/reference_versions.json") as f:
|
||||
reference_versions_list = json.load(f)
|
||||
reference_versions_list.insert(0, target_unstable)
|
||||
with open("docs-website/reference_versions.json", "w") as f:
|
||||
json.dump(reference_versions_list, f)
|
||||
|
||||
# in docusaurus.config.js, replace the target unstable version with the next unstable version
|
||||
with open("docs-website/docusaurus.config.js") as f:
|
||||
config = f.read()
|
||||
config = config.replace(f"label: '{target_unstable}'", f"label: '{next_unstable}'")
|
||||
with open("docs-website/docusaurus.config.js", "w") as f:
|
||||
f.write(config)
|
||||
@@ -0,0 +1,120 @@
|
||||
"""
|
||||
This script syncs the Haystack docs HTML files to the deepset workspace for search indexing.
|
||||
|
||||
It is used in the docs_search_sync.yml workflow.
|
||||
|
||||
1. Collects all HTML files from the docs and reference directories for the stable Haystack version.
|
||||
2. Uploads the HTML files to the deepset workspace.
|
||||
- A timestamp-based metadata field is used to track document versions in the workspace.
|
||||
3. Deletes the old HTML files from the deepset workspace.
|
||||
- Since most files are overwritten during upload, only a small number of deletions is expected.
|
||||
- In case MAX_DELETIONS_SAFETY_LIMIT is exceeded, we block the deletion.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from deepset_cloud_sdk.workflows.sync_client.files import DeepsetCloudFile, WriteMode, list_files, upload_texts
|
||||
|
||||
DEEPSET_WORKSPACE_DOCS_SEARCH = os.environ["DEEPSET_WORKSPACE_DOCS_SEARCH"]
|
||||
DEEPSET_API_KEY_DOCS_SEARCH = os.environ["DEEPSET_API_KEY_DOCS_SEARCH"]
|
||||
|
||||
# If there are more files to delete than this limit, it's likely that something went wrong in the upload process.
|
||||
MAX_DELETIONS_SAFETY_LIMIT = 20
|
||||
|
||||
|
||||
def collect_docs_files(version: int) -> list[DeepsetCloudFile]:
|
||||
"""
|
||||
Collect all HTML files from the docs and reference directories.
|
||||
|
||||
Returns a list of DeepsetCloudFile objects.
|
||||
"""
|
||||
repo_root = Path(__file__).parent.parent.parent
|
||||
build_dir = repo_root / "docs-website" / "build"
|
||||
# we want to exclude previous and temporarily unstable versions (2.x) and next version (next)
|
||||
exclude = ("2.", "next")
|
||||
|
||||
files = []
|
||||
for section in ("docs", "reference"):
|
||||
for subfolder in (build_dir / section).iterdir():
|
||||
if subfolder.is_dir() and not any(x in subfolder.name for x in exclude):
|
||||
for html_file in subfolder.rglob("*.html"):
|
||||
files.append(
|
||||
DeepsetCloudFile(
|
||||
# The build produces files like docs/agents/index.html or reference/agents-api/index.html.
|
||||
# For file names, we want to use the parent directory name (agents.html or agents-api.html)
|
||||
name=f"{html_file.parent.name}.html",
|
||||
text=html_file.read_text(),
|
||||
meta={
|
||||
"type": "api-reference" if section == "reference" else "documentation",
|
||||
"version": version,
|
||||
},
|
||||
)
|
||||
)
|
||||
return files
|
||||
|
||||
|
||||
def delete_files(file_names: list[str]) -> None:
|
||||
"""
|
||||
Delete files from the deepset workspace.
|
||||
"""
|
||||
url = f"https://api.cloud.deepset.ai/api/v1/workspaces/{DEEPSET_WORKSPACE_DOCS_SEARCH}/files"
|
||||
payload = {"names": file_names}
|
||||
headers = {"Accept": "application/json", "Authorization": f"Bearer {DEEPSET_API_KEY_DOCS_SEARCH}"}
|
||||
response = requests.delete(url, json=payload, headers=headers, timeout=300)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
version = time.time_ns()
|
||||
print(f"Docs version: {version}")
|
||||
|
||||
print("Collecting docs files from build directory")
|
||||
dc_files = collect_docs_files(version)
|
||||
print(f"Collected {len(dc_files)} docs files")
|
||||
|
||||
if len(dc_files) == 0:
|
||||
print("No docs files found. Something is wrong. Exiting.")
|
||||
sys.exit(1)
|
||||
|
||||
print("Uploading docs files to deepset")
|
||||
summary = upload_texts(
|
||||
workspace_name=DEEPSET_WORKSPACE_DOCS_SEARCH,
|
||||
files=dc_files,
|
||||
api_key=DEEPSET_API_KEY_DOCS_SEARCH,
|
||||
blocking=True, # Very important to ensure that DC is up to date when we query for deletion
|
||||
timeout_s=300,
|
||||
show_progress=True,
|
||||
write_mode=WriteMode.OVERWRITE,
|
||||
enable_parallel_processing=True,
|
||||
)
|
||||
print(f"Uploaded docs files to deepset\n{summary}")
|
||||
if summary.failed_upload_count > 0:
|
||||
print("Failed to upload some docs files. Stopping to prevent risky deletion of old files.")
|
||||
sys.exit(1)
|
||||
|
||||
print("Listing old docs files from deepset")
|
||||
odata_filter = f"version lt '{version}'"
|
||||
old_files_names = [
|
||||
f.name
|
||||
for batch in list_files(
|
||||
workspace_name=DEEPSET_WORKSPACE_DOCS_SEARCH, api_key=DEEPSET_API_KEY_DOCS_SEARCH, odata_filter=odata_filter
|
||||
)
|
||||
for f in batch
|
||||
]
|
||||
|
||||
print(f"Found {len(old_files_names)} old files to delete")
|
||||
if len(old_files_names) > MAX_DELETIONS_SAFETY_LIMIT:
|
||||
print(
|
||||
f"Found >{MAX_DELETIONS_SAFETY_LIMIT} old files to delete. "
|
||||
"Stopping because something could have gone wrong in the upload process."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if len(old_files_names) > 0:
|
||||
print("Deleting old docs files from deepset")
|
||||
delete_files(old_files_names)
|
||||
print("Deleted old docs files from deepset")
|
||||
@@ -0,0 +1,46 @@
|
||||
import ast
|
||||
import hashlib
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def docstrings_checksum(python_files: Iterator[Path]) -> str:
|
||||
"""
|
||||
Calculate the checksum of the docstrings in the given Python files.
|
||||
"""
|
||||
files_content = (f.read_text() for f in python_files)
|
||||
trees = (ast.parse(c) for c in files_content)
|
||||
|
||||
# Get all docstrings from async functions, functions,
|
||||
# classes and modules definitions
|
||||
docstrings = []
|
||||
for tree in trees:
|
||||
for node in ast.walk(tree):
|
||||
if not isinstance(node, (ast.AsyncFunctionDef, ast.FunctionDef, ast.ClassDef, ast.Module)):
|
||||
# Skip all node types that can't have docstrings to prevent failures
|
||||
continue
|
||||
docstring = ast.get_docstring(node)
|
||||
if docstring:
|
||||
docstrings.append(docstring)
|
||||
|
||||
# Sort them to be safe, since ast.walk() returns
|
||||
# nodes in no specified order.
|
||||
# See https://docs.python.org/3/library/ast.html#ast.walk
|
||||
docstrings.sort()
|
||||
|
||||
return hashlib.md5(str(docstrings).encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", help="Haystack root folder", required=True, type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get all Haystack and rest_api python files
|
||||
root: Path = args.root.absolute()
|
||||
haystack_files = root.glob("haystack/**/*.py")
|
||||
|
||||
md5 = docstrings_checksum(haystack_files)
|
||||
print(md5)
|
||||
Executable
+171
@@ -0,0 +1,171 @@
|
||||
#!/bin/bash
|
||||
# parse_validate_version.sh - Parse and validate version for release
|
||||
#
|
||||
# Usage: ./parse_validate_version.sh <version>
|
||||
# Output: Writes to $GITHUB_OUTPUT if set, otherwise to stdout
|
||||
#
|
||||
# Example:
|
||||
# ./parse_validate_version.sh v2.99.0-rc1
|
||||
#
|
||||
# This script is used in the release.yml workflow to parse and validate the version to be released.
|
||||
# Covers several checks to prevent accidental releases of incorrect versions.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Helpers ---
|
||||
|
||||
fail() {
|
||||
echo ""
|
||||
echo -e "❌ $1"
|
||||
echo ""
|
||||
exit 1
|
||||
}
|
||||
|
||||
ok() {
|
||||
echo "✅ $1"
|
||||
}
|
||||
|
||||
tag_exists() {
|
||||
git tag -l "$1" | grep -q "^$1$"
|
||||
}
|
||||
|
||||
branch_exists() {
|
||||
git ls-remote --heads origin "$1" | grep -q "$1"
|
||||
}
|
||||
|
||||
# --- Parse and validate version ---
|
||||
|
||||
VERSION="${1#v}" # Strip 'v' prefix
|
||||
|
||||
echo ""
|
||||
echo "ℹ️ Validating: ${1}"
|
||||
echo ""
|
||||
|
||||
if [[ ! "${VERSION}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-rc([0-9]+))?$ ]]; then
|
||||
fail "Invalid version format: $1\n\n"\
|
||||
"Expected format: vMAJOR.MINOR.PATCH or vMAJOR.MINOR.PATCH-rcN\n"\
|
||||
"Examples: v2.99.0-rc1, v2.99.0, v2.99.1-rc1"
|
||||
fi
|
||||
ok "Version format is valid"
|
||||
|
||||
MAJOR="${BASH_REMATCH[1]}"
|
||||
MINOR="${BASH_REMATCH[2]}"
|
||||
PATCH="${BASH_REMATCH[3]}"
|
||||
RC_NUM="${BASH_REMATCH[5]:-0}"
|
||||
|
||||
if [[ "${RC_NUM}" == "0" && "${VERSION}" == *"-rc0" ]]; then
|
||||
fail "Cannot release rc0\n\n"\
|
||||
"rc0 is an internal marker created automatically during branch-off.\n"\
|
||||
"Release candidates start at rc1."
|
||||
fi
|
||||
|
||||
MAJOR_MINOR="${MAJOR}.${MINOR}"
|
||||
RELEASE_BRANCH="v${MAJOR_MINOR}.x"
|
||||
TAG="v${VERSION}"
|
||||
|
||||
IS_RC="false"
|
||||
[[ "${RC_NUM}" != "0" ]] && IS_RC="true"
|
||||
|
||||
IS_FIRST_RC="false"
|
||||
if [[ "${PATCH}" == "0" && "${RC_NUM}" == "1" ]]; then
|
||||
IS_FIRST_RC="true"
|
||||
fi
|
||||
|
||||
# 1. Tag must not already exist
|
||||
if tag_exists "${TAG}"; then
|
||||
fail "Version ${TAG} was already released\n\n"\
|
||||
"Each version can only be released once.\n"\
|
||||
"To publish changes, release the next RC or patch version."
|
||||
fi
|
||||
ok "Tag ${TAG} does not exist"
|
||||
|
||||
# 2. Checks based on release type
|
||||
if [[ "${IS_FIRST_RC}" == "true" ]]; then
|
||||
# First RC of minor: branch must NOT exist yet
|
||||
if branch_exists "${RELEASE_BRANCH}"; then
|
||||
fail "Branch ${RELEASE_BRANCH} already exists\n\n"\
|
||||
"The first RC of a minor (e.g., v${MAJOR_MINOR}.0-rc1) creates the release branch.\n"\
|
||||
"Since the branch exists, this minor was likely already started.\n"\
|
||||
"Did you mean to release the next RC (rc2, rc3...) or a patch (v${MAJOR_MINOR}.1-rc1)?"
|
||||
fi
|
||||
ok "Branch ${RELEASE_BRANCH} does not exist"
|
||||
|
||||
# First RC of minor: VERSION.txt must contain rc0
|
||||
EXPECTED="${MAJOR_MINOR}.0-rc0"
|
||||
ACTUAL=$(cat VERSION.txt)
|
||||
if [[ "${ACTUAL}" != "${EXPECTED}" ]]; then
|
||||
ACTUAL_MINOR=$(echo "${ACTUAL}" | cut -d. -f1,2)
|
||||
fail "Cannot release v${MAJOR_MINOR}.0-rc1 from this branch\n\n"\
|
||||
"The main branch is prepared for version ${ACTUAL_MINOR}, not ${MAJOR_MINOR}.\n"\
|
||||
"Check that you're releasing the correct version."
|
||||
fi
|
||||
ok "VERSION.txt = ${EXPECTED}"
|
||||
|
||||
else
|
||||
# Not first RC: branch MUST exist
|
||||
if ! branch_exists "${RELEASE_BRANCH}"; then
|
||||
if [[ "${PATCH}" == "0" ]]; then
|
||||
fail "Branch ${RELEASE_BRANCH} does not exist\n\n"\
|
||||
"For subsequent RCs (rc2, rc3...), the release branch must already exist.\n"\
|
||||
"Release the first RC (v${MAJOR_MINOR}.0-rc1) first to create the branch."
|
||||
else
|
||||
fail "Branch ${RELEASE_BRANCH} does not exist\n\n"\
|
||||
"For patch releases, the release branch must already exist.\n"\
|
||||
"The minor version (v${MAJOR_MINOR}.0) must be released before any patches."
|
||||
fi
|
||||
fi
|
||||
ok "Branch ${RELEASE_BRANCH} exists"
|
||||
|
||||
# Subsequent RC (rc2, rc3...): previous RC must exist
|
||||
if [[ "${RC_NUM}" -gt 1 ]]; then
|
||||
PREV_RC_NUM=$((RC_NUM - 1))
|
||||
PREV_TAG="v${MAJOR_MINOR}.${PATCH}-rc${PREV_RC_NUM}"
|
||||
if ! tag_exists "${PREV_TAG}"; then
|
||||
fail "Cannot release v${MAJOR_MINOR}.${PATCH}-rc${RC_NUM}\n\n"\
|
||||
"Previous RC (${PREV_TAG}) was not found.\n"\
|
||||
"RC versions must be sequential. Release rc${PREV_RC_NUM} first."
|
||||
fi
|
||||
ok "Previous tag ${PREV_TAG} exists"
|
||||
fi
|
||||
|
||||
# Final release: at least one RC must exist
|
||||
if [[ "${RC_NUM}" == "0" ]]; then
|
||||
RC_TAGS=$(git tag -l "v${MAJOR_MINOR}.${PATCH}-rc*" | grep -v "\-rc0$" || true)
|
||||
if [[ -z "${RC_TAGS}" ]]; then
|
||||
fail "Cannot release stable version v${MAJOR_MINOR}.${PATCH}\n\n"\
|
||||
"No release candidate found for this version.\n"\
|
||||
"Stable releases require at least one RC first (e.g., v${MAJOR_MINOR}.${PATCH}-rc1)."
|
||||
fi
|
||||
LAST_RC=$(echo "${RC_TAGS}" | sort -V | tail -n1)
|
||||
ok "Found RC: ${LAST_RC}"
|
||||
|
||||
# Check Tests workflow passed (only if credentials available)
|
||||
if [[ -n "${GH_TOKEN:-}" && -n "${GITHUB_REPOSITORY:-}" ]]; then
|
||||
RC_SHA=$(git rev-list -n 1 "${LAST_RC}")
|
||||
RESULT=$(gh api "/repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${RC_SHA}&status=success" \
|
||||
--jq '.workflow_runs[] | select(.name == "Tests") | .conclusion' 2>/dev/null || true)
|
||||
if [[ -z "${RESULT}" ]]; then
|
||||
fail "Cannot release stable version v${MAJOR_MINOR}.${PATCH}\n\n"\
|
||||
"Tests did not pass on the last RC (${LAST_RC}).\n"\
|
||||
"Wait for tests to complete, or release a new RC with fixes."
|
||||
fi
|
||||
ok "Tests passed on ${LAST_RC}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
ok "All validations passed!"
|
||||
echo ""
|
||||
|
||||
# --- Output to GITHUB_OUTPUT (or stdout for local testing) ---
|
||||
|
||||
OUTPUT_FILE="${GITHUB_OUTPUT:-/dev/stdout}"
|
||||
|
||||
{
|
||||
echo "version=${VERSION}"
|
||||
echo "major_minor=${MAJOR_MINOR}"
|
||||
echo "release_branch=${RELEASE_BRANCH}"
|
||||
echo "is_rc=${IS_RC}"
|
||||
echo "is_first_rc=${IS_FIRST_RC}"
|
||||
} >> "${OUTPUT_FILE}"
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
#!/bin/bash
|
||||
# prepare_release_notification.sh - Prepare Slack notification for release outcome
|
||||
#
|
||||
# Requires: VERSION, RUN_URL, HAS_FAILURE, GH_TOKEN, GITHUB_REPOSITORY
|
||||
# Optional: IS_RC, IS_FIRST_RC, MAJOR_MINOR, GITHUB_URL, PYPI_URL, DOCKER_URL,
|
||||
# BUMP_VERSION_PR_URL, DC_PIPELINE_TEMPLATES_PR_URL, DC_CUSTOM_NODES_PR_URL,
|
||||
# HAYSTACK_RUNTIME_PR_URL, GITHUB_WORKSPACE
|
||||
# Output: slack_payload.json
|
||||
#
|
||||
# This script is used in the release.yml workflow to prepare the notification payload
|
||||
# sent to Slack after a release completes (success or failure).
|
||||
# Text uses Slack mrkdwn format: *bold*, <url|label> for links.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PAYLOAD_FILE="${GITHUB_WORKSPACE:-/tmp}/slack_payload.json"
|
||||
|
||||
write_payload() {
|
||||
jq -n --arg text "$TXT" '{
|
||||
text: $text,
|
||||
blocks: [{ type: "section", text: { type: "mrkdwn", text: $text } }]
|
||||
}' > "$PAYLOAD_FILE"
|
||||
}
|
||||
|
||||
if [[ "${HAS_FAILURE}" == "true" ]]; then
|
||||
TXT=":red_circle: Release *${VERSION}* failed"
|
||||
TXT+=$'\n'"Check workflow run for details: <${RUN_URL}|View Logs>"
|
||||
write_payload
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Success case
|
||||
|
||||
TXT=":white_check_mark: Release *${VERSION}* completed successfully"
|
||||
|
||||
# Add artifact URLs if available
|
||||
if [[ -n "${GITHUB_URL:-}" || -n "${PYPI_URL:-}" || -n "${DOCKER_URL:-}" ]]; then
|
||||
TXT+=$'\n\n:package: *Artifacts:*'
|
||||
[[ -n "${GITHUB_URL:-}" ]] && TXT+=$'\n'"- <${GITHUB_URL}|Release notes (GitHub)>"
|
||||
[[ -n "${PYPI_URL:-}" ]] && TXT+=$'\n'"- <${PYPI_URL}|PyPI>"
|
||||
[[ -n "${DOCKER_URL:-}" ]] && TXT+=$'\n'"- <${DOCKER_URL}|Docker>"
|
||||
fi
|
||||
|
||||
# For RCs, include link to the Tests workflow run
|
||||
if [[ "${IS_RC:-}" == "true" ]]; then
|
||||
COMMIT_SHA=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${VERSION}" --jq '.sha' 2>/dev/null || echo "")
|
||||
if [[ -n "${COMMIT_SHA}" ]]; then
|
||||
TESTS_RUN=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs?head_sha=${COMMIT_SHA}" \
|
||||
--jq '.workflow_runs[] | select(.name == "Tests") | .html_url' 2>/dev/null | head -1 || echo "")
|
||||
if [[ -n "${TESTS_RUN}" ]]; then
|
||||
TXT+=$'\n\n'":test_tube: <${TESTS_RUN}|Haystack Tests>"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# For first RC, include the PRs to merge from branch-off
|
||||
if [[ "${IS_FIRST_RC:-}" == "true" && -n "${BUMP_VERSION_PR_URL:-}" ]]; then
|
||||
TXT+=$'\n\n'":clipboard: *PRs to merge:*"
|
||||
TXT+=$'\n'"- <${BUMP_VERSION_PR_URL}|Bump unstable version and create unstable docs>"
|
||||
fi
|
||||
|
||||
# For RCs, include Platform test PRs
|
||||
if [[ "${IS_RC:-}" == "true" ]]; then
|
||||
PLATFORM_PRS=""
|
||||
[[ -n "${DC_PIPELINE_TEMPLATES_PR_URL:-}" ]] && PLATFORM_PRS+=$'\n'"- <${DC_PIPELINE_TEMPLATES_PR_URL}|dc-pipeline-templates>"
|
||||
[[ -n "${DC_CUSTOM_NODES_PR_URL:-}" ]] && PLATFORM_PRS+=$'\n'"- <${DC_CUSTOM_NODES_PR_URL}|deepset-cloud-custom-nodes>"
|
||||
[[ -n "${HAYSTACK_RUNTIME_PR_URL:-}" ]] && PLATFORM_PRS+=$'\n'"- <${HAYSTACK_RUNTIME_PR_URL}|haystack-runtime>"
|
||||
if [[ -n "${PLATFORM_PRS}" ]]; then
|
||||
TXT+=$'\n\n'":factory: *Test PRs opened on Platform:*${PLATFORM_PRS}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For RCs, request Platform Engineering taking over testing
|
||||
if [[ "${IS_RC:-}" == "true" ]]; then
|
||||
TXT+=$'\n\n'"This release is marked as a Release Candidate."
|
||||
TXT+=$'\n'"Notify #deepset-platform-engineering channel on Slack that the RC is available"
|
||||
TXT+=" and that Platform tests pass/fail, linking the PRs opened on the Platform repositories."
|
||||
fi
|
||||
|
||||
# For final minor releases (vX.Y.0), include the docs promotion PR
|
||||
if [[ "${VERSION}" =~ ^v[0-9]+\.[0-9]+\.0$ && -n "${MAJOR_MINOR:-}" ]]; then
|
||||
PROMOTE_DOCS_PR_URL=$(gh pr list --repo "${GITHUB_REPOSITORY}" \
|
||||
--head "promote-unstable-docs-${MAJOR_MINOR}" --json url --jq '.[0].url' 2>/dev/null || echo "")
|
||||
if [[ -n "${PROMOTE_DOCS_PR_URL}" ]]; then
|
||||
TXT+=$'\n\n'":clipboard: *PRs to merge:*"
|
||||
TXT+=$'\n'"- <${PROMOTE_DOCS_PR_URL}|Promote unstable docs>"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For final releases (not RCs), include info about pushing release notes to website
|
||||
if [[ "${IS_RC:-}" != "true" ]]; then
|
||||
TXT+=$'\n\n'":memo: After refining and finalizing release notes, push them to Haystack website:"
|
||||
TXT+=$'\n'"\`gh workflow run push_release_notes_to_website.yml -R deepset-ai/haystack -f version=${VERSION}\`"
|
||||
fi
|
||||
|
||||
write_payload
|
||||
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
This script promotes an unstable documentation version to a stable version at the time of a new Haystack release.
|
||||
|
||||
To understand how unstable doc versions are created, see create_unstable_docs_docusaurus.py.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
VERSION_VALIDATOR = re.compile(r"^[0-9]+\.[0-9]+$")
|
||||
MAX_STABLE_VERSIONS = 5
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-v", "--version", help="The version to promote to stable (e.g. 2.20).", required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
if VERSION_VALIDATOR.match(args.version) is None:
|
||||
sys.exit("Version must be formatted like so <major>.<minor>")
|
||||
|
||||
target_version = f"{args.version}" # e.g., "2.20" - the target release version
|
||||
major, minor = args.version.split(".")
|
||||
|
||||
target_unstable = f"{target_version}-unstable" # e.g., "2.20-unstable"
|
||||
previous_stable = f"{major}.{int(minor) - 1}" # e.g., "2.19" - previous stable release
|
||||
|
||||
versions = [
|
||||
folder.replace("version-", "")
|
||||
for folder in os.listdir("docs-website/versioned_docs")
|
||||
if os.path.isdir(os.path.join("docs-website/versioned_docs", folder))
|
||||
]
|
||||
|
||||
if target_version in versions:
|
||||
sys.exit(f"{target_version} already exists (already released). Aborting.")
|
||||
if target_unstable not in versions:
|
||||
sys.exit(f"Can't find version {target_unstable} to promote to {target_version}")
|
||||
|
||||
print(f"Promoting unstable version {target_unstable} to stable version {target_version}")
|
||||
|
||||
### Docusaurus updates
|
||||
|
||||
# move versioned_docs/version-target_unstable to versioned_docs/version-target_version
|
||||
shutil.move(
|
||||
f"docs-website/versioned_docs/version-{target_unstable}",
|
||||
f"docs-website/versioned_docs/version-{target_version}",
|
||||
)
|
||||
|
||||
# move reference_versioned_docs/version-target_unstable to reference_versioned_docs/version-target_version
|
||||
shutil.move(
|
||||
f"docs-website/reference_versioned_docs/version-{target_unstable}",
|
||||
f"docs-website/reference_versioned_docs/version-{target_version}",
|
||||
)
|
||||
|
||||
# move versioned_sidebars/version-target_unstable-sidebars.json
|
||||
# to versioned_sidebars/version-target_version-sidebars.json
|
||||
shutil.move(
|
||||
f"docs-website/versioned_sidebars/version-{target_unstable}-sidebars.json",
|
||||
f"docs-website/versioned_sidebars/version-{target_version}-sidebars.json",
|
||||
)
|
||||
|
||||
# move reference_versioned_sidebars/version-target_unstable-sidebars.json
|
||||
# to reference_versioned_sidebars/version-target_version-sidebars.json
|
||||
shutil.move(
|
||||
f"docs-website/reference_versioned_sidebars/version-{target_unstable}-sidebars.json",
|
||||
f"docs-website/reference_versioned_sidebars/version-{target_version}-sidebars.json",
|
||||
)
|
||||
|
||||
# replace unstable version with stable version in versions.json
|
||||
with open("docs-website/versions.json") as f:
|
||||
versions_list = json.load(f)
|
||||
versions_list[versions_list.index(target_unstable)] = target_version
|
||||
with open("docs-website/versions.json", "w") as f:
|
||||
json.dump(versions_list, f)
|
||||
|
||||
# replace unstable version with stable version in reference_versions.json
|
||||
with open("docs-website/reference_versions.json") as f:
|
||||
reference_versions_list = json.load(f)
|
||||
reference_versions_list[reference_versions_list.index(target_unstable)] = target_version
|
||||
with open("docs-website/reference_versions.json", "w") as f:
|
||||
json.dump(reference_versions_list, f)
|
||||
|
||||
# in docusaurus.config.js, replace previous stable version with the target version
|
||||
with open("docs-website/docusaurus.config.js") as f:
|
||||
config = f.read()
|
||||
config = config.replace(f"lastVersion: '{previous_stable}'", f"lastVersion: '{target_version}'") # "2.19" -> "2.20"
|
||||
with open("docs-website/docusaurus.config.js", "w") as f:
|
||||
f.write(config)
|
||||
|
||||
# regenerate vercel.json redirects for inactive versions (those beyond the top MAX_STABLE_VERSIONS)
|
||||
with open("docs-website/versions.json") as f:
|
||||
updated_versions = json.load(f)
|
||||
stable_versions = [v for v in updated_versions if not v.endswith("-unstable")]
|
||||
inactive_versions = stable_versions[MAX_STABLE_VERSIONS:]
|
||||
redirects = []
|
||||
for v in inactive_versions:
|
||||
redirects.append({"source": f"/docs/{v}/:slug*", "destination": "/docs/:slug*", "permanent": True})
|
||||
redirects.append({"source": f"/reference/{v}/:slug*", "destination": "/reference/:slug*", "permanent": True})
|
||||
|
||||
with open("docs-website/vercel.json") as f:
|
||||
vercel_config = json.load(f)
|
||||
|
||||
existing_redirects = vercel_config.get("redirects", [])
|
||||
existing_sources = {r.get("source") for r in existing_redirects}
|
||||
|
||||
for r in redirects:
|
||||
if r["source"] not in existing_sources:
|
||||
existing_redirects.append(r)
|
||||
|
||||
vercel_config["redirects"] = existing_redirects
|
||||
with open("docs-website/vercel.json", "w") as f:
|
||||
json.dump(vercel_config, f, indent=2)
|
||||
f.write("\n")
|
||||
print(f"Updated vercel.json with {len(redirects)} redirect(s) for inactive versions: {inactive_versions}")
|
||||
@@ -0,0 +1,53 @@
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import toml
|
||||
|
||||
matcher = re.compile(r"farm-haystack\[(.+)\]")
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="pyproject_to_requirements.py", description="Convert pyproject.toml to requirements.txt"
|
||||
)
|
||||
parser.add_argument("pyproject_path")
|
||||
parser.add_argument("--extra", default="")
|
||||
|
||||
|
||||
def resolve(target: str, extras: dict, results: set) -> None:
|
||||
"""
|
||||
Resolve the dependencies for a given target.
|
||||
"""
|
||||
if target not in extras:
|
||||
results.add(target)
|
||||
return
|
||||
|
||||
for t in extras[target]:
|
||||
m = matcher.match(t)
|
||||
if m:
|
||||
for i in m.group(1).split(","):
|
||||
resolve(i, extras, results)
|
||||
else:
|
||||
resolve(t, extras, results)
|
||||
|
||||
|
||||
def main(pyproject_path: Path, extra: str = "") -> None:
|
||||
"""
|
||||
Convert a pyproject.toml file to a requirements.txt file.
|
||||
"""
|
||||
content = toml.load(pyproject_path)
|
||||
# basic set of dependencies
|
||||
deps = set(content["project"]["dependencies"])
|
||||
|
||||
if extra:
|
||||
extras = content["project"]["optional-dependencies"]
|
||||
resolve(extra, extras, deps)
|
||||
|
||||
sys.stdout.write("\n".join(sorted(deps)))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
pyproject_path = Path(args.pyproject_path).absolute()
|
||||
|
||||
main(pyproject_path, args.extra)
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Update the haystack-ai version in the deepset-cloud-custom-nodes uv.lock file.
|
||||
|
||||
Fetches sdist/wheel hashes from PyPI and updates the haystack-ai package entry,
|
||||
preserving the existing lock file formatting.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
import tomlkit
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("version", help="Version to update to (e.g. 2.26.1-rc1)")
|
||||
parser.add_argument("lock_file", help="Path to uv.lock")
|
||||
args = parser.parse_args()
|
||||
|
||||
# PEP 440 normalized version for filenames
|
||||
new_version = args.version.replace("-", "")
|
||||
|
||||
# Fetch hashes from PyPI
|
||||
pypi_data = json.load(urllib.request.urlopen(f"https://pypi.org/pypi/haystack-ai/{args.version}/json"))
|
||||
sdist_sha = wheel_sha = None
|
||||
for u in pypi_data["urls"]:
|
||||
if u["packagetype"] == "sdist":
|
||||
sdist_sha = u["digests"]["sha256"]
|
||||
elif u["packagetype"] == "bdist_wheel":
|
||||
wheel_sha = u["digests"]["sha256"]
|
||||
if not sdist_sha or not wheel_sha:
|
||||
sys.exit("Could not find sdist or wheel hashes on PyPI")
|
||||
|
||||
with open(args.lock_file) as f:
|
||||
data = tomlkit.load(f)
|
||||
|
||||
found = False
|
||||
for pkg in data["package"]:
|
||||
if pkg["name"] == "haystack-ai":
|
||||
old_version = pkg["version"]
|
||||
|
||||
pkg["version"] = new_version
|
||||
|
||||
pkg["sdist"]["url"] = pkg["sdist"]["url"].replace(old_version, new_version)
|
||||
pkg["sdist"]["hash"] = f"sha256:{sdist_sha}"
|
||||
|
||||
wheel = pkg["wheels"][0]
|
||||
wheel["url"] = wheel["url"].replace(old_version, new_version)
|
||||
wheel["hash"] = f"sha256:{wheel_sha}"
|
||||
|
||||
found = True
|
||||
print(f"Updated haystack-ai from {old_version} to {new_version}")
|
||||
break
|
||||
|
||||
if not found:
|
||||
sys.exit("haystack-ai package not found in uv.lock")
|
||||
|
||||
with open(args.lock_file, "w") as f:
|
||||
tomlkit.dump(data, f)
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
# wait_for_workflows.sh - Wait for tag-triggered workflows to complete
|
||||
#
|
||||
# Usage: ./wait_for_workflows.sh <tag> <workflow_name1> [workflow_name2] ...
|
||||
# Requires: GH_TOKEN and GITHUB_REPOSITORY environment variables
|
||||
#
|
||||
# Example:
|
||||
# ./wait_for_workflows.sh v2.19.0 "Project release on PyPi" "Docker image release"
|
||||
#
|
||||
# This script is used in the release.yml workflow to wait for the workflows triggered by a specific release tag to
|
||||
# successfully complete.
|
||||
|
||||
|
||||
# With the default values, we wait for 20 minutes
|
||||
MAX_ATTEMPTS="${MAX_ATTEMPTS:-40}"
|
||||
SLEEP_SECONDS="${SLEEP_SECONDS:-30}"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${GH_TOKEN:-}" ]] || [[ -z "${GITHUB_REPOSITORY:-}" ]]; then
|
||||
echo "❌ GH_TOKEN and GITHUB_REPOSITORY must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG="$1"
|
||||
shift
|
||||
WORKFLOWS=("$@")
|
||||
|
||||
|
||||
# Get commit SHA from tag
|
||||
TAG_SHA=$(git rev-list -n 1 "${TAG}" 2>/dev/null) || {
|
||||
echo "❌ Tag ${TAG} not found"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Tag ${TAG} (commit: ${TAG_SHA:0:7})"
|
||||
echo ""
|
||||
|
||||
wait_for_workflow() {
|
||||
local name="$1"
|
||||
echo "⏳ Waiting for: $name"
|
||||
|
||||
for ((i=1; i<=MAX_ATTEMPTS; i++)); do
|
||||
jq_filter="[.workflow_runs[] | select(.head_sha == \"${TAG_SHA}\" and .name == \"${name}\")]
|
||||
| sort_by(.created_at) | last"
|
||||
|
||||
result=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs" \
|
||||
--jq "$jq_filter" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$result" ]]; then
|
||||
echo " Attempt $i/$MAX_ATTEMPTS: not started yet..."
|
||||
sleep $SLEEP_SECONDS
|
||||
continue
|
||||
fi
|
||||
|
||||
status=$(echo "$result" | jq -r '.status')
|
||||
conclusion=$(echo "$result" | jq -r '.conclusion')
|
||||
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
if [[ "$conclusion" == "success" ]]; then
|
||||
echo "✅ $name completed"
|
||||
return 0
|
||||
else
|
||||
echo "❌ $name failed: $conclusion"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " Attempt $i/$MAX_ATTEMPTS: $status..."
|
||||
sleep $SLEEP_SECONDS
|
||||
done
|
||||
|
||||
echo "❌ $name: timeout after $((MAX_ATTEMPTS * SLEEP_SECONDS / 60)) minutes"
|
||||
return 1
|
||||
}
|
||||
|
||||
for workflow in "${WORKFLOWS[@]}"; do
|
||||
wait_for_workflow "$workflow" || exit 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✅ All workflows completed"
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Approve and merge API reference sync PRs
|
||||
|
||||
# Automatically approve and merge API reference sync PRs from Haystack, Haystack Core Integrations,
|
||||
# and Haystack Experimental
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "docs-website/reference/**"
|
||||
- "docs-website/reference_versioned_docs/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
jobs:
|
||||
auto-approve-and-merge:
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
if: |
|
||||
github.event.pull_request.user.login == 'HaystackBot' &&
|
||||
startsWith(github.event.pull_request.head.ref, 'sync-docusaurus-api-reference') &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Approve PR
|
||||
run: gh pr review --approve ${{ github.event.pull_request.number }} --repo ${{ github.repository }}
|
||||
|
||||
- name: Enable auto-merge
|
||||
run: gh pr merge --squash --auto ${{ github.event.pull_request.number }} --repo ${{ github.repository }}
|
||||
@@ -0,0 +1,99 @@
|
||||
name: Branch off
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
bump_version_pr_url:
|
||||
description: 'URL of the bump version PR'
|
||||
value: ${{ jobs.branch-off.outputs.bump_version_pr_url }}
|
||||
env:
|
||||
PYTHON_VERSION: "3.10"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
branch-off:
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
bump_version_pr_url: ${{ steps.create-pr.outputs.pull-request-url }}
|
||||
|
||||
steps:
|
||||
- name: Checkout this repo
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
ref: main
|
||||
# Persist the bot token so the branch/tag pushes below authenticate as
|
||||
# HaystackBot (a ruleset-bypass actor) instead of the default
|
||||
# github-actions[bot], which only has contents:read and is blocked by
|
||||
# the release rulesets.
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
|
||||
- name: Define all versions
|
||||
id: versions
|
||||
shell: bash
|
||||
run: |
|
||||
# example: 2.20.0-rc0 in VERSION.txt -> 2.20
|
||||
echo "current_version_major_minor=$(cut -d "." -f 1,2 < VERSION.txt)" >> "$GITHUB_OUTPUT"
|
||||
# example: 2.20.0-rc0 in VERSION.txt -> 2.21.0-rc0
|
||||
echo "next_version_rc0=$(awk -F. '/[0-9]+\./{$2++;print}' OFS=. < VERSION.txt)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create release branch and tag
|
||||
shell: bash
|
||||
env:
|
||||
# We use the HAYSTACK_BOT_TOKEN here so the PR created by the step will
|
||||
# trigger required workflows and can be merged by anyone
|
||||
GITHUB_TOKEN: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Create the release branch from main
|
||||
git checkout -b v${{ steps.versions.outputs.current_version_major_minor }}.x
|
||||
git push -u origin v${{ steps.versions.outputs.current_version_major_minor }}.x
|
||||
|
||||
# Tag the branch-off point with the next version rc0 to mark start of next dev cycle.
|
||||
# The tag points to this commit (before VERSION.txt is bumped).
|
||||
# This is intentional for reno to work properly.
|
||||
git tag "v${{ steps.versions.outputs.next_version_rc0 }}" -m "v${{ steps.versions.outputs.next_version_rc0 }}"
|
||||
git push --tags
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Prepare changes for main
|
||||
shell: bash
|
||||
run: |
|
||||
git checkout main
|
||||
|
||||
# Bump VERSION.txt to next version rc0
|
||||
echo "${{ steps.versions.outputs.next_version_rc0 }}" > VERSION.txt
|
||||
|
||||
# Generate unstable docs for Docusaurus
|
||||
python ./.github/utils/create_unstable_docs_docusaurus.py --new-version ${{ steps.versions.outputs.current_version_major_minor }}
|
||||
|
||||
- name: Create PR to bump unstable version and create unstable docs
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
commit-message: "Bump unstable version and create unstable docs"
|
||||
branch: bump-version
|
||||
base: main
|
||||
title: "Bump unstable version and create unstable docs"
|
||||
add-paths: |
|
||||
VERSION.txt
|
||||
docs-website
|
||||
body: |
|
||||
This PR:
|
||||
- Bumps the unstable version to `${{ steps.versions.outputs.next_version_rc0 }}`
|
||||
- Creates the unstable docs for Haystack ${{ steps.versions.outputs.current_version_major_minor }}
|
||||
|
||||
You can inspect the docs preview (two unstable versions will be available) and merge it.
|
||||
labels: "ignore-for-release-notes"
|
||||
reviewers: "${{ github.actor }}"
|
||||
@@ -0,0 +1,57 @@
|
||||
name: ClusterFuzzLite PR fuzzing
|
||||
|
||||
# Short, code-change-scoped fuzzing run on PRs that touch fuzzed code or the
|
||||
# fuzzing setup. Catches regressions and crashes introduced by a change.
|
||||
# Continuous/batch fuzzing can be added later as a separate scheduled workflow.
|
||||
#
|
||||
# Scoped to the modules the harnesses actually exercise rather than all of
|
||||
# `haystack/**`, so the (build-heavy) job doesn't run on every library PR:
|
||||
# - Pipeline.loads -> core/pipeline, core/serialization, marshal
|
||||
# - Document.from_dict -> dataclasses
|
||||
# - document_matches_filter -> utils/filters
|
||||
# Deep, transitively-reached regressions are better caught by a scheduled batch
|
||||
# run; random YAML/JSON essentially never reaches haystack/components.
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "haystack/core/pipeline/**"
|
||||
- "haystack/core/serialization.py"
|
||||
- "haystack/core/errors.py"
|
||||
- "haystack/marshal/**"
|
||||
- "haystack/dataclasses/**"
|
||||
- "haystack/utils/filters.py"
|
||||
- "test/fuzz/**"
|
||||
- ".clusterfuzzlite/**"
|
||||
- ".github/workflows/cflite_pr.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pr-fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: cflite-pr-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Build Fuzzers
|
||||
id: build
|
||||
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
|
||||
with:
|
||||
language: python
|
||||
sanitizer: address
|
||||
# Needed so build_fuzzers can check out the PR base for `mode: code-change`
|
||||
# diffing in the Run Fuzzers step below.
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run Fuzzers
|
||||
id: run
|
||||
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fuzz-seconds: 120
|
||||
mode: code-change
|
||||
sanitizer: address
|
||||
# Crashes fail the job and are uploaded as artifacts. SARIF upload is
|
||||
# disabled to keep the token least-privilege (no security-events: write).
|
||||
output-sarif: false
|
||||
@@ -0,0 +1,99 @@
|
||||
name: Check API reference changes
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "haystack/**/*.py"
|
||||
- "pydoc/*.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-api-reference-build:
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Detect API reference changes
|
||||
id: changed
|
||||
shell: python
|
||||
run: |
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, ".github/utils")
|
||||
from docstrings_checksum import docstrings_checksum
|
||||
|
||||
def git(*args):
|
||||
result = subprocess.run(["git", *args], capture_output=True, text=True)
|
||||
return result.stdout.strip(), result.returncode
|
||||
|
||||
base_sha, _ = git("rev-parse", "HEAD^1")
|
||||
diff_output, _ = git("diff", "--name-only", f"{base_sha}...HEAD")
|
||||
changed_files = set(diff_output.splitlines())
|
||||
|
||||
needs_check = False
|
||||
|
||||
# If any pydoc config changed, always rebuild
|
||||
if any(f.startswith("pydoc/") and f.endswith(".yml") for f in changed_files):
|
||||
needs_check = True
|
||||
|
||||
# If Python files changed, compare docstring checksums
|
||||
if not needs_check and any(f.startswith("haystack/") and f.endswith(".py") for f in changed_files):
|
||||
runner_temp = os.environ["RUNNER_TEMP"]
|
||||
base_worktree = os.path.join(runner_temp, "base")
|
||||
_, rc = git("worktree", "add", base_worktree, base_sha)
|
||||
|
||||
pr_checksum = docstrings_checksum(Path(".").glob("haystack/**/*.py"))
|
||||
base_checksum = ""
|
||||
if rc == 0:
|
||||
base_checksum = docstrings_checksum(Path(base_worktree).glob("haystack/**/*.py"))
|
||||
|
||||
if pr_checksum != base_checksum:
|
||||
needs_check = True
|
||||
|
||||
print(f"API reference check needed: {needs_check}")
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
|
||||
f.write(f"needs_check={str(needs_check).lower()}\n")
|
||||
|
||||
- name: Install Hatch
|
||||
if: steps.changed.outputs.needs_check == 'true'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch --uploaded-prior-to=P1D
|
||||
|
||||
- name: Generate API references
|
||||
if: steps.changed.outputs.needs_check == 'true'
|
||||
run: hatch run docs
|
||||
|
||||
- name: Set up Node.js
|
||||
if: steps.changed.outputs.needs_check == 'true'
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Run Docusaurus md/mdx checker
|
||||
if: steps.changed.outputs.needs_check == 'true'
|
||||
working-directory: tmp_api_reference
|
||||
run: |
|
||||
# docusaurus-mdx-checker is a package that is not frequently updated. Its dependency katex sometimes ships a
|
||||
# broken ESM build, where a __VERSION__ placeholder is left unresolved, causing a ReferenceError at import time.
|
||||
# Node 22+ prefers ESM when available. We force CJS (CommonJS) resolution to use the working katex build.
|
||||
# This should be safe because docusaurus-mdx-checker and its dependencies provide CJS builds.
|
||||
export NODE_OPTIONS="--conditions=require"
|
||||
npx docusaurus-mdx-checker@3.0.0 -v || {
|
||||
echo ""
|
||||
echo "For common MDX problems, see https://docusaurus.io/blog/preparing-your-site-for-docusaurus-v3#common-mdx-problems"
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
name: CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Cancel a superseded run for the same ref (e.g. a force-push to a PR) so we
|
||||
# don't burn CI minutes analyzing commits that are already stale.
|
||||
concurrency:
|
||||
group: codeql-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 360
|
||||
permissions:
|
||||
# Required to upload code-scanning results to the Security tab.
|
||||
security-events: write
|
||||
# Required by codeql-action to read the workflow/CI config.
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Haystack is a Python project; Python is interpreted, so no build is
|
||||
# needed. Add `javascript-typescript` here if the docs-website JS code
|
||||
# should also be scanned.
|
||||
- language: python
|
||||
build-mode: none
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Add comment about test coverage to PRs
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- "Tests"
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: py-cov-action/python-coverage-comment-action@5d8df5979747514c914e1c5a12335a7cf9a2745f # v4.1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
MINIMUM_GREEN: 90
|
||||
MINIMUM_ORANGE: 60
|
||||
@@ -0,0 +1,88 @@
|
||||
name: Docker image release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '.github/workflows/docker_release.yml'
|
||||
- 'docker/**'
|
||||
- 'haystack/**'
|
||||
- 'pyproject.toml'
|
||||
- 'VERSION.txt'
|
||||
tags:
|
||||
- "v2.[0-9]+.[0-9]+*"
|
||||
|
||||
env:
|
||||
DOCKER_REPO_NAME: deepset/haystack
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build base image
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'deepset-ai'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USER }}
|
||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||
with:
|
||||
images: $DOCKER_REPO_NAME
|
||||
|
||||
- name: Detect stable version
|
||||
run: |
|
||||
if [[ "${{ steps.meta.outputs.version }}" =~ ^v2\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "IS_STABLE=true" >> "$GITHUB_ENV"
|
||||
echo "Stable version detected"
|
||||
else
|
||||
echo "Not a stable version"
|
||||
fi
|
||||
- name: Build base images
|
||||
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0
|
||||
env:
|
||||
IMAGE_TAG_SUFFIX: ${{ steps.meta.outputs.version }}
|
||||
HAYSTACK_VERSION: ${{ steps.meta.outputs.version }}
|
||||
with:
|
||||
source: ./docker
|
||||
targets: base
|
||||
push: true
|
||||
|
||||
- name: Test base image
|
||||
run: |
|
||||
EXPECTED_VERSION=$(cat VERSION.txt)
|
||||
if [[ $EXPECTED_VERSION == *"-"* ]]; then
|
||||
EXPECTED_VERSION=$(cut -d '-' -f 1 < VERSION.txt)$(cut -d '-' -f 2 < VERSION.txt)
|
||||
fi
|
||||
TAG="base-${{ steps.meta.outputs.version }}"
|
||||
|
||||
PLATFORM="linux/amd64"
|
||||
VERSION=$(docker run --platform "$PLATFORM" --rm "deepset/haystack:$TAG" python -c"from haystack.version import __version__; print(__version__)")
|
||||
[[ "$VERSION" = "$EXPECTED_VERSION" ]] || echo "::error 'Haystack version in deepset/haystack:$TAG image for $PLATFORM is different from expected'"
|
||||
|
||||
PLATFORM="linux/arm64"
|
||||
VERSION=$(docker run --platform "$PLATFORM" --rm "deepset/haystack:$TAG" python -c"from haystack.version import __version__; print(__version__)")
|
||||
[[ "$VERSION" = "$EXPECTED_VERSION" ]] || echo "::error 'Haystack version in deepset/haystack:$TAG image for $PLATFORM is different from expected'"
|
||||
|
||||
# Remove image after test to avoid filling the GitHub runner and prevent its failure
|
||||
docker rmi "deepset/haystack:$TAG"
|
||||
@@ -0,0 +1,74 @@
|
||||
name: Test Python snippets in docs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "17 3 * * *" # daily at 03:17 UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
haystack_version:
|
||||
description: "Haystack version to test against (e.g., 2.16.1, main)"
|
||||
required: false
|
||||
default: "main"
|
||||
type: string
|
||||
|
||||
env:
|
||||
HATCH_VERSION: "1.16.5"
|
||||
PYTHON_VERSION: "3.11"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-docs-snippets:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
|
||||
AZURE_OPENAI_AD_TOKEN: ${{ secrets.AZURE_OPENAI_AD_TOKEN }}
|
||||
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
|
||||
CORE_AZURE_CS_ENDPOINT: ${{ secrets.CORE_AZURE_CS_ENDPOINT }}
|
||||
HF_API_TOKEN: ${{ secrets.HUGGINGFACE_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
SERPERDEV_API_KEY: ${{ secrets.SERPERDEV_API_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install system dependencies
|
||||
run: sudo apt-get install -y ffmpeg
|
||||
|
||||
- name: Install Hatch
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
|
||||
- name: Generate API reference for Docusaurus
|
||||
run: hatch run docs
|
||||
|
||||
- name: Install base dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests toml --uploaded-prior-to=P1D
|
||||
|
||||
- name: Run snippet tests (verbose)
|
||||
shell: bash
|
||||
run: |
|
||||
hatch -e test env run -- python docs-website/scripts/test_python_snippets.py --verbose tmp_api_reference/
|
||||
|
||||
notify-slack-on-failure:
|
||||
if: failure() && github.ref_name == 'main'
|
||||
needs:
|
||||
- test-docs-snippets
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: deepset-ai/notify-slack-action@a65def0c8bf91d6520286ab34280151c76a5a008 # v1.1.0
|
||||
with:
|
||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL_NOTIFICATIONS }}
|
||||
@@ -0,0 +1,60 @@
|
||||
name: Docs Search Sync
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Activate this workflow manually
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
# Running this workflow multiple times in parallel can cause issues with files uploads and deletions.
|
||||
concurrency:
|
||||
group: docs-search-sync
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
docs-search-sync:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Haystack repo
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install Docusaurus and build docs-website
|
||||
working-directory: docs-website
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
npm rebuild sharp
|
||||
npm run build
|
||||
|
||||
- name: Install script dependencies
|
||||
# sniffio is needed because of https://github.com/deepset-ai/deepset-cloud-sdk/issues/286
|
||||
# we pin pyrate-limiter due to https://github.com/deepset-ai/deepset-cloud-sdk/issues/295
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install deepset-cloud-sdk sniffio requests "pyrate-limiter<4" --uploaded-prior-to=P1D
|
||||
|
||||
- name: Update new docs to Search pipeline and remove outdated docs
|
||||
env:
|
||||
DEEPSET_WORKSPACE_DOCS_SEARCH: ${{ secrets.DEEPSET_WORKSPACE_DOCS_SEARCH }}
|
||||
DEEPSET_API_KEY_DOCS_SEARCH: ${{ secrets.DEEPSET_API_KEY_DOCS_SEARCH }}
|
||||
run: python ./.github/utils/docs_search_sync.py
|
||||
|
||||
- name: Notify Slack on nightly failure
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
uses: deepset-ai/notify-slack-action@a65def0c8bf91d6520286ab34280151c76a5a008 # v1.1.0
|
||||
with:
|
||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL_NOTIFICATIONS }}
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Detect docstrings edit
|
||||
|
||||
# This workflow runs in the *untrusted* pull_request context: it has a read-only
|
||||
# token and no access to secrets, so checking out the PR head here is safe.
|
||||
# It only computes whether docstrings changed and hands the result to the
|
||||
# "Apply docstrings label" workflow (workflow_run), which holds the write token
|
||||
# but never checks out untrusted code. See:
|
||||
# https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "haystack/**/*.py"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.11"
|
||||
|
||||
jobs:
|
||||
detect:
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
steps:
|
||||
- name: Checkout base commit
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Get base docstrings
|
||||
id: base-docstrings
|
||||
run: |
|
||||
CHECKSUM=$(python .github/utils/docstrings_checksum.py --root "${{ github.workspace }}")
|
||||
echo "checksum=$CHECKSUM" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Checkout HEAD commit
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Get HEAD docstrings
|
||||
id: head-docstrings
|
||||
run: |
|
||||
CHECKSUM=$(python .github/utils/docstrings_checksum.py --root "${{ github.workspace }}")
|
||||
echo "checksum=$CHECKSUM" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Write result
|
||||
# The follow-up workflow reads these values; it never checks out the PR code.
|
||||
run: |
|
||||
mkdir -p result
|
||||
echo "${{ github.event.pull_request.number }}" > result/pr_number
|
||||
if [ "${{ steps.base-docstrings.outputs.checksum }}" != "${{ steps.head-docstrings.outputs.checksum }}" ]; then
|
||||
echo "true" > result/should_label
|
||||
else
|
||||
echo "false" > result/should_label
|
||||
fi
|
||||
|
||||
- name: Upload result
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: docstring-label-result
|
||||
path: result/
|
||||
retention-days: 1
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Apply docstrings label
|
||||
|
||||
# Runs after "Detect docstrings edit" completes. This workflow holds the write
|
||||
# token but only consumes the data artifact produced by the untrusted run — it
|
||||
# never checks out PR code, so it is safe under the workflow_run trigger.
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Detect docstrings edit"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-slim
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
actions: read
|
||||
steps:
|
||||
- name: Download result
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: docstring-label-result
|
||||
path: result
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Add label
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
SHOULD_LABEL=$(cat result/should_label)
|
||||
PR_NUMBER=$(cat result/pr_number)
|
||||
if [ "$SHOULD_LABEL" = "true" ]; then
|
||||
gh pr edit "$PR_NUMBER" --repo "${{ github.repository }}" --add-label "type:documentation"
|
||||
else
|
||||
echo "Docstrings unchanged, nothing to label."
|
||||
fi
|
||||
@@ -0,0 +1,66 @@
|
||||
name: Sync docs with Docusaurus
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pydoc/**"
|
||||
- "haystack/**"
|
||||
- ".github/workflows/docusaurus_sync.yml"
|
||||
|
||||
env:
|
||||
HATCH_VERSION: "1.16.5"
|
||||
PYTHON_VERSION: "3.11"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Haystack repo
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
|
||||
- name: Generate API reference for Docusaurus
|
||||
run: hatch run docs
|
||||
|
||||
- name: Sync generated API reference to docs folder
|
||||
run: |
|
||||
SOURCE_PATH="tmp_api_reference"
|
||||
DEST_PATH="docs-website/reference/haystack-api"
|
||||
|
||||
echo "Syncing from $SOURCE_PATH to $DEST_PATH"
|
||||
mkdir -p $DEST_PATH
|
||||
# Using rsync to copy files. This will also remove files in dest that are no longer in source.
|
||||
rsync -av --delete --exclude='.git/' "$SOURCE_PATH/" "$DEST_PATH/"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
commit-message: "Sync Haystack API reference on Docusaurus"
|
||||
branch: sync-docusaurus-api-reference
|
||||
base: main
|
||||
title: "docs: sync Haystack API reference on Docusaurus"
|
||||
add-paths: |
|
||||
docs-website/reference/haystack-api
|
||||
body: |
|
||||
This PR syncs the Haystack API reference on Docusaurus. Just approve and merge it.
|
||||
@@ -0,0 +1,52 @@
|
||||
name: end-to-end
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Activate this workflow manually
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
paths:
|
||||
- "e2e/**/*.py"
|
||||
- ".github/workflows/e2e.yml"
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.10"
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
HATCH_VERSION: "1.16.5"
|
||||
# we use HF_TOKEN instead of HF_API_TOKEN to work around a Hugging Face bug
|
||||
# see https://github.com/deepset-ai/haystack/issues/9552
|
||||
HF_TOKEN: ${{ secrets.HUGGINGFACE_API_KEY }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
run:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
|
||||
- name: Run tests
|
||||
run: hatch run test:e2e
|
||||
|
||||
- name: Notify Slack on nightly failure
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
uses: deepset-ai/notify-slack-action@a65def0c8bf91d6520286ab34280151c76a5a008 # v1.1.0
|
||||
with:
|
||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL_NOTIFICATIONS }}
|
||||
@@ -0,0 +1,104 @@
|
||||
name: Project release on Github
|
||||
|
||||
on:
|
||||
workflow_dispatch: # this is useful to re-generate the release page without a new tag being pushed
|
||||
push:
|
||||
tags:
|
||||
- "v2.[0-9]+.[0-9]+*"
|
||||
# Ignore release versions tagged with -rc0 suffix
|
||||
- "!v2.[0-9]+.[0-9]-rc0"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
generate-notes:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # ncipollo/release-action creates the GitHub release
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0 # slow but needed by reno
|
||||
|
||||
- name: Parse version
|
||||
id: version
|
||||
run: |
|
||||
echo "current_release=$(awk -F \\- '{print $1}' < VERSION.txt)" >> "$GITHUB_OUTPUT"
|
||||
echo "current_pre_release=$(awk -F \\- '{print $2}' < VERSION.txt)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Install reno
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install "reno<5" --uploaded-prior-to=P1D
|
||||
|
||||
# Remove next version rc0 tag in the CI environment to prevent reno from assigning notes to future releases.
|
||||
# This ensures release notes are correctly aggregated for the current version.
|
||||
# This is a workaround. Can be removed if the release process is fully aligned with reno.
|
||||
- name: Delete next version rc0 tag in the CI environment
|
||||
run: |
|
||||
# Parse version X.Y.Z and increment Y for next minor version
|
||||
IFS='.' read -r MAJOR MINOR _ <<< "${{ steps.version.outputs.current_release }}"
|
||||
NEXT_MINOR=$((MINOR + 1))
|
||||
NEXT_TAG="v${MAJOR}.${NEXT_MINOR}.0-rc0"
|
||||
|
||||
if git rev-parse --verify "$NEXT_TAG" >/dev/null 2>&1; then
|
||||
git tag -d "$NEXT_TAG"
|
||||
echo "Deleted local tag $NEXT_TAG"
|
||||
else
|
||||
echo "Tag $NEXT_TAG does not exist locally"
|
||||
fi
|
||||
|
||||
- name: Generate release notes
|
||||
env:
|
||||
|
||||
EARLIEST_VERSION: v${{ steps.version.outputs.current_release }}-rc1
|
||||
run: |
|
||||
reno report --no-show-source --ignore-cache --earliest-version "$EARLIEST_VERSION" -o relnotes.rst
|
||||
|
||||
- name: Convert to Markdown
|
||||
uses: docker://pandoc/core:3.8@sha256:c7127705b9d84c1b4acbbe411a86e3a6d67ac57870654dc378b6fe636ab747df
|
||||
with:
|
||||
args: "--from rst --to gfm --syntax-highlighting=none --wrap=none relnotes.rst -o relnotes.md"
|
||||
|
||||
# We copy the relnotes file since the original one cannot be modified due to permissions
|
||||
- name: Copy relnotes file
|
||||
run: |
|
||||
cat relnotes.md > enhanced_relnotes.md
|
||||
|
||||
- name: Add contributor list
|
||||
# only for minor releases and minor release candidates (not bugfix releases)
|
||||
if: endsWith(steps.version.outputs.current_release, '.0')
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
START: v${{ steps.version.outputs.current_release }}-rc0
|
||||
END: ${{ github.ref_name }}
|
||||
run: |
|
||||
JQ_EXPR='[.commits[].author.login]
|
||||
| map(select(. != null and . != "HaystackBot" and . != "dependabot[bot]" and . != "github-actions[bot]"))
|
||||
| unique
|
||||
| sort_by(ascii_downcase)
|
||||
| map("@\(.)")
|
||||
| join(", ")'
|
||||
CONTRIBUTORS=$(gh api "repos/deepset-ai/haystack/compare/$START...$END" \
|
||||
--jq "$JQ_EXPR") || { echo "Unable to fetch contributors"; exit 1; }
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "## 💙 Big thank you to everyone who contributed to this release!"
|
||||
echo ""
|
||||
echo "$CONTRIBUTORS"
|
||||
} >> enhanced_relnotes.md
|
||||
|
||||
- name: Debug
|
||||
run: |
|
||||
cat enhanced_relnotes.md
|
||||
|
||||
- uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
|
||||
with:
|
||||
bodyFile: "enhanced_relnotes.md"
|
||||
prerelease: ${{ steps.version.outputs.current_pre_release != '' }}
|
||||
allowUpdates: true
|
||||
@@ -0,0 +1,17 @@
|
||||
name: "Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -0,0 +1,69 @@
|
||||
name: License Compliance
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**/pyproject.toml"
|
||||
- ".github/workflows/license_compliance.yml"
|
||||
# Since we test PRs, there is no need to run the workflow at each
|
||||
# merge on `main`. Let's use a cron job instead.
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # every day at midnight
|
||||
env:
|
||||
PYTHON_VERSION: "3.10"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
license_check_direct:
|
||||
name: Direct dependencies only
|
||||
env:
|
||||
REQUIREMENTS_FILE: requirements_direct.txt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Get direct dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install toml --uploaded-prior-to=P1D
|
||||
python .github/utils/pyproject_to_requirements.py pyproject.toml > ${{ env.REQUIREMENTS_FILE }}
|
||||
|
||||
- name: Check Licenses
|
||||
id: license_check_report
|
||||
uses: pilosus/action-pip-license-checker@e909b0226ff49d3235c99c4585bc617f49fff16a # v3.1.0
|
||||
with:
|
||||
github-token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||
requirements: ${{ env.REQUIREMENTS_FILE }}
|
||||
fail: "Copyleft,Other,Error"
|
||||
# Exclusions in the vanilla distribution must be explicitly motivated
|
||||
# - tqdm is MLP but there are no better alternatives
|
||||
# - typing_extensions>=4.13.0 has a Python Software Foundation License 2.0 but pip-license-checker does not recognize it
|
||||
# (https://github.com/pilosus/pip-license-checker/issues/143)
|
||||
exclude: "(?i)^(tqdm|typing_extensions).*"
|
||||
|
||||
# We keep the license inventory on FOSSA
|
||||
- name: Send license report to Fossa
|
||||
uses: fossas/fossa-action@29693cc50323968e039056be419b32989fc5880c # v2.0.0
|
||||
continue-on-error: true # not critical
|
||||
with:
|
||||
api-key: ${{ secrets.FOSSA_LICENSE_SCAN_TOKEN }}
|
||||
|
||||
- name: Print report
|
||||
if: ${{ always() }}
|
||||
run: echo "${{ steps.license_check_report.outputs.report }}"
|
||||
|
||||
- name: Notify Slack on failure
|
||||
if: failure()
|
||||
uses: deepset-ai/notify-slack-action@a65def0c8bf91d6520286ab34280151c76a5a008 # v1.1.0
|
||||
with:
|
||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL_NOTIFICATIONS }}
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Nightly pre-release on PyPI
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run at midnight UTC every day
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
HATCH_VERSION: "1.16.5"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
nightly-release:
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
# Always build from main for consistency (scheduled and manual runs)
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: main
|
||||
fetch-depth: 1
|
||||
|
||||
# Reads VERSION.txt, strips any -rcN suffix, and appends .devYYYYMMDDHHMMSS
|
||||
# (e.g. 2.25.0.dev20250217000000) so each run gets a unique, PEP 440–valid pre-release version.
|
||||
- name: Set nightly version
|
||||
id: set-version
|
||||
run: |
|
||||
BASE_VERSION=$(sed 's/-rc[0-9]*$//' VERSION.txt)
|
||||
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||||
NIGHTLY_VERSION="${BASE_VERSION}.dev${TIMESTAMP}"
|
||||
echo "version=${NIGHTLY_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "${NIGHTLY_VERSION}" > VERSION.txt
|
||||
echo "Building haystack-ai version: ${NIGHTLY_VERSION}"
|
||||
|
||||
- name: Install Hatch
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
|
||||
- name: Build Haystack
|
||||
run: hatch build
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Track issues with Github project
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add new issues to project for triage
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
|
||||
with:
|
||||
project-url: https://github.com/orgs/deepset-ai/projects/5
|
||||
github-token: ${{ secrets.GH_PROJECT_PAT }}
|
||||
@@ -0,0 +1,65 @@
|
||||
name: Release new minor version docs
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
# Trigger this only for new minor version tags (e.g. v2.99.0)
|
||||
- "v[0-9]+.[0-9]+.0"
|
||||
# Exclude 1.x tags
|
||||
- "!v1.[0-9]+.[0-9]+"
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.10"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
promote:
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Checkout this repo
|
||||
# use VERSION.txt file from main branch
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: main
|
||||
|
||||
- name: Get version to release
|
||||
id: version
|
||||
shell: bash
|
||||
# We only need `major.minor`. At this point, VERSION.txt contains the next version.
|
||||
# For example, if we are releasing 2.20.0, VERSION.txt contains 2.21.0-rc0.
|
||||
run: |
|
||||
MAJOR=$(cut -d "." -f 1 < VERSION.txt)
|
||||
MINOR=$(cut -d "." -f 2 < VERSION.txt)
|
||||
MINOR=$((MINOR - 1))
|
||||
echo "version=${MAJOR}.${MINOR}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Promote unstable docs for Docusaurus
|
||||
run: |
|
||||
python ./.github/utils/promote_unstable_docs_docusaurus.py --version ${{ steps.version.outputs.version }}
|
||||
|
||||
- name: Create Pull Request with Docusaurus docs updates
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
commit-message: "Promote unstable docs for Haystack ${{ steps.version.outputs.version }}"
|
||||
branch: promote-unstable-docs-${{ steps.version.outputs.version }}
|
||||
base: main
|
||||
title: "docs: promote unstable docs for Haystack ${{ steps.version.outputs.version }}"
|
||||
add-paths: |
|
||||
docs-website
|
||||
body: |
|
||||
This PR promotes the unstable docs for Haystack ${{ steps.version.outputs.version }} to stable.
|
||||
It is expected to run at the time of the release.
|
||||
You can inspect the docs preview and merge it. There should now be only one unstable version representing the next (main) branch.
|
||||
# This workflow is triggered by a tag pushed by the HaystackBot in release.yml > create-release-tag.
|
||||
# GitHub requires reviewers to be different from the PR author, so setting `github.actor`
|
||||
# would fail (it would request a review from the HaystackBot itself).
|
||||
# So we don't set any reviewers and instead notify the Release Manager
|
||||
# (see .github/utils/prepare_release_notification.sh).
|
||||
@@ -0,0 +1,65 @@
|
||||
name: Push release notes to website
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Haystack version (vX.Y.Z)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
push-release-notes-to-website:
|
||||
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Checkout Haystack home repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: deepset-ai/haystack-home
|
||||
|
||||
- name: Get release notes and add frontmatter
|
||||
id: release_notes
|
||||
|
||||
run: |
|
||||
VERSION_NUMBER="${VERSION:1}"
|
||||
RELEASE_DATE=$(gh release view "$VERSION" --repo deepset-ai/haystack --json publishedAt --jq '.publishedAt | split("T")[0]')
|
||||
RELEASE_NOTES_PATH="content/release-notes/$VERSION_NUMBER.md"
|
||||
|
||||
{
|
||||
echo "---"
|
||||
echo "title: Haystack $VERSION_NUMBER"
|
||||
echo "description: Release notes for Haystack $VERSION_NUMBER"
|
||||
echo "toc: True"
|
||||
echo "date: $RELEASE_DATE"
|
||||
echo "last_updated: $RELEASE_DATE"
|
||||
echo 'tags: ["Release Notes"]'
|
||||
echo "link: https://github.com/deepset-ai/haystack/releases/tag/$VERSION"
|
||||
echo "---"
|
||||
echo ""
|
||||
} > "$RELEASE_NOTES_PATH"
|
||||
|
||||
gh release view "$VERSION" --repo deepset-ai/haystack --json body --jq '.body' >> "$RELEASE_NOTES_PATH"
|
||||
|
||||
cat "$RELEASE_NOTES_PATH"
|
||||
|
||||
- name: Create Pull Request to Haystack Home
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
commit-message: "Add release notes for Haystack ${{ env.VERSION }}"
|
||||
branch: add-release-notes-for-haystack-${{ env.VERSION }}
|
||||
base: main
|
||||
title: "docs: add release notes for Haystack ${{ env.VERSION }}"
|
||||
add-paths: |
|
||||
content/release-notes
|
||||
body: |
|
||||
This PR adds the release notes for Haystack ${{ env.VERSION }} to the website.
|
||||
reviewers: "${{ github.actor }}"
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Project release on PyPi
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v[0-9]+.[0-9]+.[0-9]+*"
|
||||
# We must not release versions tagged with -rc0 suffix
|
||||
- "!v[0-9]+.[0-9]+.[0-9]-rc0"
|
||||
|
||||
env:
|
||||
HATCH_VERSION: "1.16.5"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release-on-pypi:
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Hatch
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
|
||||
- name: Build Haystack
|
||||
run: hatch build
|
||||
|
||||
- name: Publish on PyPi
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
||||
@@ -0,0 +1,302 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to release (e.g., v2.99.0-rc1 or v2.99.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
# Only one release workflow runs at a time; additional runs are queued.
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
authorize:
|
||||
# Releasing acts as HaystackBot (a ruleset-bypass actor), so the ruleset
|
||||
# cannot enforce *who* started the release. Gate on the triggering user's
|
||||
# role instead: only maintainers/admins may run this workflow, even though
|
||||
# workflow_dispatch itself is available to anyone with write access.
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Verify the triggering user may release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
ACTOR: ${{ github.triggering_actor }}
|
||||
run: |
|
||||
ROLE=$(gh api "repos/${{ github.repository }}/collaborators/${ACTOR}/permission" --jq '.role_name')
|
||||
echo "::notice::${ACTOR} has repository role '${ROLE}'"
|
||||
case "$ROLE" in
|
||||
admin|maintain)
|
||||
echo "✅ ${ACTOR} is authorized to trigger a release." ;;
|
||||
*)
|
||||
echo "::error::${ACTOR} has role '${ROLE}'. A release can only be triggered by a user with the 'maintain' or 'admin' role."
|
||||
exit 1 ;;
|
||||
esac
|
||||
|
||||
parse-validate-version:
|
||||
needs: ["authorize"]
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
version: ${{ steps.parse-validate.outputs.version }}
|
||||
major_minor: ${{ steps.parse-validate.outputs.major_minor }}
|
||||
release_branch: ${{ steps.parse-validate.outputs.release_branch }}
|
||||
is_rc: ${{ steps.parse-validate.outputs.is_rc }}
|
||||
is_first_rc: ${{ steps.parse-validate.outputs.is_first_rc }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0 # needed to fetch tags and branches
|
||||
- name: Parse and validate version
|
||||
id: parse-validate
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: .github/utils/parse_validate_version.sh "$VERSION"
|
||||
|
||||
branch-off:
|
||||
needs: ["parse-validate-version"]
|
||||
if: needs.parse-validate-version.outputs.is_first_rc == 'true'
|
||||
uses: ./.github/workflows/branch_off.yml
|
||||
# https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows#passing-secrets-to-nested-workflows
|
||||
secrets: inherit
|
||||
|
||||
create-release-tag:
|
||||
needs: ["parse-validate-version", "branch-off"]
|
||||
if: always() && needs.parse-validate-version.result == 'success' && needs.branch-off.result != 'failure'
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0 # needed to fetch tags and branches
|
||||
# use this token so the created tag triggers workflows (does not happen with the default github.token)
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
- name: Update VERSION.txt and create tag
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git checkout ${{ needs.parse-validate-version.outputs.release_branch }}
|
||||
git pull origin ${{ needs.parse-validate-version.outputs.release_branch }}
|
||||
|
||||
echo "${{ needs.parse-validate-version.outputs.version }}" > VERSION.txt
|
||||
git add VERSION.txt
|
||||
git commit -m "bump version to ${{ needs.parse-validate-version.outputs.version }}"
|
||||
git push origin ${{ needs.parse-validate-version.outputs.release_branch }}
|
||||
|
||||
TAG="v${{ needs.parse-validate-version.outputs.version }}"
|
||||
git tag -m "$TAG" "$TAG"
|
||||
git push origin "$TAG"
|
||||
|
||||
check-artifacts:
|
||||
needs: ["parse-validate-version", "create-release-tag"]
|
||||
if: always() && needs.parse-validate-version.result == 'success' && needs.create-release-tag.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
github_url: ${{ steps.set-outputs.outputs.github_url }}
|
||||
pypi_url: ${{ steps.set-outputs.outputs.pypi_url }}
|
||||
docker_url: ${{ steps.set-outputs.outputs.docker_url }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ needs.parse-validate-version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0 # needed to fetch tags and branches
|
||||
|
||||
- name: Wait for release workflows
|
||||
run: |
|
||||
.github/utils/wait_for_workflows.sh "v${{ env.VERSION }}" \
|
||||
"Project release on PyPi" \
|
||||
"Project release on Github" \
|
||||
"Docker image release"
|
||||
|
||||
- name: Check artifacts
|
||||
run: |
|
||||
check() {
|
||||
for _ in {1..5}; do curl -sf "$2" > /dev/null && echo "✅ $1" && return 0; sleep 30; done
|
||||
echo "❌ $1 not found" && return 1
|
||||
}
|
||||
check "GitHub Release" "https://api.github.com/repos/${{ github.repository }}/releases/tags/v${{ env.VERSION }}"
|
||||
check "PyPI package" "https://pypi.org/pypi/haystack-ai/${{ env.VERSION }}/json"
|
||||
check "Docker image" "https://hub.docker.com/v2/repositories/deepset/haystack/tags/base-v${{ env.VERSION }}"
|
||||
|
||||
- name: Set artifact URLs
|
||||
id: set-outputs
|
||||
run: |
|
||||
{
|
||||
echo "github_url=https://github.com/${{ github.repository }}/releases/tag/v${{ env.VERSION }}"
|
||||
echo "pypi_url=https://pypi.org/project/haystack-ai/${{ env.VERSION }}/"
|
||||
echo "docker_url=https://hub.docker.com/r/deepset/haystack/tags?name=base-v${{ env.VERSION }}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
bump-dc-pipeline-templates:
|
||||
needs: ["parse-validate-version", "check-artifacts"]
|
||||
if: always() && needs.check-artifacts.result == 'success' && needs.parse-validate-version.outputs.is_rc == 'true'
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
pr_url: ${{ steps.create-pr.outputs.pull-request-url }}
|
||||
env:
|
||||
VERSION: ${{ needs.parse-validate-version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout dc-pipeline-templates
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: deepset-ai/dc-pipeline-templates
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
|
||||
- name: Update haystack pin
|
||||
run: sed -i "s/haystack-ai>=.*/haystack-ai>=${VERSION}/" requirements-test.txt
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
commit-message: "Bump haystack to ${{ env.VERSION }}"
|
||||
branch: bump-haystack-${{ env.VERSION }}
|
||||
base: main
|
||||
title: "chore: bump haystack to ${{ env.VERSION }}"
|
||||
add-paths: requirements-test.txt
|
||||
body: |
|
||||
Bump haystack pin to `${{ env.VERSION }}` for platform testing.
|
||||
|
||||
bump-deepset-cloud-custom-nodes:
|
||||
needs: ["parse-validate-version", "check-artifacts"]
|
||||
if: always() && needs.check-artifacts.result == 'success' && needs.parse-validate-version.outputs.is_rc == 'true'
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
pr_url: ${{ steps.create-pr.outputs.pull-request-url }}
|
||||
env:
|
||||
VERSION: ${{ needs.parse-validate-version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout haystack (for utils)
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
path: haystack
|
||||
sparse-checkout: .github/utils/update_haystack_dc_custom_nodes.py
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Checkout deepset-cloud-custom-nodes
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: deepset-ai/deepset-cloud-custom-nodes
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
path: deepset-cloud-custom-nodes
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Install tomlkit
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tomlkit --uploaded-prior-to=P1D
|
||||
|
||||
- name: Update haystack-ai in uv.lock
|
||||
run: python haystack/.github/utils/update_haystack_dc_custom_nodes.py "${{ env.VERSION }}" deepset-cloud-custom-nodes/uv.lock
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
path: deepset-cloud-custom-nodes
|
||||
commit-message: "Bump haystack to ${{ env.VERSION }}"
|
||||
branch: bump-haystack-${{ env.VERSION }}
|
||||
base: main
|
||||
title: "chore: bump haystack to ${{ env.VERSION }}"
|
||||
add-paths: uv.lock
|
||||
body: |
|
||||
Bump haystack pin to `${{ env.VERSION }}` for platform testing.
|
||||
|
||||
bump-haystack-runtime:
|
||||
needs: ["parse-validate-version", "check-artifacts"]
|
||||
if: always() && needs.check-artifacts.result == 'success' && needs.parse-validate-version.outputs.is_rc == 'true'
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
pr_url: ${{ steps.wait-pr.outputs.pr_url }}
|
||||
env:
|
||||
VERSION: ${{ needs.parse-validate-version.outputs.version }}
|
||||
steps:
|
||||
- name: Trigger "Update package version" workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
run: |
|
||||
gh workflow run update-package-version.yaml \
|
||||
-R deepset-ai/haystack-runtime \
|
||||
-f haystack_version="${{ env.VERSION }}"
|
||||
|
||||
- name: Wait for PR
|
||||
id: wait-pr
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
run: |
|
||||
TRIGGER_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
for i in $(seq 1 30); do
|
||||
PR_URL=$(gh pr list -R deepset-ai/haystack-runtime \
|
||||
--head "bump/hs${{ env.VERSION }}" \
|
||||
--json url,createdAt \
|
||||
--jq ".[] | select(.createdAt >= \"$TRIGGER_TIME\") | .url")
|
||||
if [[ -n "$PR_URL" ]]; then
|
||||
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
|
||||
echo "Found PR: $PR_URL"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i: PR not found yet, waiting 10s..."
|
||||
sleep 10
|
||||
done
|
||||
echo "PR not found after 5 minutes"
|
||||
|
||||
notify:
|
||||
needs:
|
||||
- "parse-validate-version"
|
||||
- "branch-off"
|
||||
- "create-release-tag"
|
||||
- "check-artifacts"
|
||||
- "bump-dc-pipeline-templates"
|
||||
- "bump-deepset-cloud-custom-nodes"
|
||||
- "bump-haystack-runtime"
|
||||
if: always()
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Prepare release notification
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
HAS_FAILURE: ${{ contains(needs.*.result, 'failure') }}
|
||||
IS_RC: ${{ needs.parse-validate-version.outputs.is_rc }}
|
||||
IS_FIRST_RC: ${{ needs.parse-validate-version.outputs.is_first_rc }}
|
||||
MAJOR_MINOR: ${{ needs.parse-validate-version.outputs.major_minor }}
|
||||
GITHUB_URL: ${{ needs.check-artifacts.outputs.github_url }}
|
||||
PYPI_URL: ${{ needs.check-artifacts.outputs.pypi_url }}
|
||||
DOCKER_URL: ${{ needs.check-artifacts.outputs.docker_url }}
|
||||
BUMP_VERSION_PR_URL: ${{ needs.branch-off.outputs.bump_version_pr_url }}
|
||||
DC_PIPELINE_TEMPLATES_PR_URL: ${{ needs.bump-dc-pipeline-templates.outputs.pr_url }}
|
||||
DC_CUSTOM_NODES_PR_URL: ${{ needs.bump-deepset-cloud-custom-nodes.outputs.pr_url }}
|
||||
HAYSTACK_RUNTIME_PR_URL: ${{ needs.bump-haystack-runtime.outputs.pr_url }}
|
||||
run: .github/utils/prepare_release_notification.sh
|
||||
|
||||
- name: Send release notification to Slack
|
||||
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
|
||||
with:
|
||||
webhook: ${{ secrets.SLACK_WEBHOOK_URL_RELEASE }}
|
||||
webhook-type: incoming-webhook
|
||||
payload-file-path: slack_payload.json
|
||||
@@ -0,0 +1,85 @@
|
||||
name: Check Release Notes
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
- labeled
|
||||
- unlabeled
|
||||
paths:
|
||||
- "**.py"
|
||||
- "pyproject.toml"
|
||||
- "!.github/**/*.py"
|
||||
- "releasenotes/notes/*.yaml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
reno:
|
||||
runs-on: ubuntu-slim
|
||||
env:
|
||||
PYTHON_VERSION: "3.10"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# With the default value of 1, there are corner cases where tj-actions/changed-files
|
||||
# fails with a `no merge base` error
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
- name: Get release note files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: releasenotes/notes/*.yaml
|
||||
|
||||
- name: Check release notes
|
||||
if: steps.changed-files.outputs.any_changed == 'false' && !contains( github.event.pull_request.labels.*.name, 'ignore-for-release-notes')
|
||||
run: |
|
||||
# Check if any of the commit messages contain tags ci/docs/test
|
||||
if git log --pretty=%s origin/main..HEAD | grep -E '^(ci:|docs:|test:)' > /dev/null; then
|
||||
echo "Skipping release note check for commits with 'ci:', 'docs:', or 'test:' tags."
|
||||
else
|
||||
echo "::error::The release notes file is missing, please add one or attach the label 'ignore-for-release-notes' to this PR."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify release notes formatting
|
||||
if: steps.changed-files.outputs.any_changed == 'true' && !contains( github.event.pull_request.labels.*.name, 'ignore-for-release-notes')
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install "reno<5" --uploaded-prior-to=P1D
|
||||
reno lint . # it is not possible to pass a list of files to reno lint
|
||||
|
||||
- name: Check reStructuredText code formatting
|
||||
if: steps.changed-files.outputs.any_changed == 'true' && !contains( github.event.pull_request.labels.*.name, 'ignore-for-release-notes')
|
||||
shell: python
|
||||
run: |
|
||||
files = "${{ steps.changed-files.outputs.all_changed_files }}".split()
|
||||
errors = []
|
||||
|
||||
for filepath in files:
|
||||
with open(filepath) as f:
|
||||
for line_no, line in enumerate(f, start=1):
|
||||
# Check for triple backticks (Markdown code blocks)
|
||||
if "```" in line:
|
||||
err = (f"Format error in {filepath}:{line_no}: "
|
||||
"Found triple backticks. Use reStructuredText code block directive instead: .. code:: python")
|
||||
errors.append(err)
|
||||
|
||||
# Check for single backticks (Markdown inline code)
|
||||
if "`" in line.replace("```", "").replace("``", ""):
|
||||
err = (f"Format error in {filepath}:{line_no}: "
|
||||
"Found single backticks. Use double backticks (``code``) for inline code.")
|
||||
errors.append(err)
|
||||
|
||||
if errors:
|
||||
raise Exception("\n".join(errors))
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Check Release Notes
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
- labeled
|
||||
- unlabeled
|
||||
paths-ignore:
|
||||
- "**.py"
|
||||
- "pyproject.toml"
|
||||
- "!.github/**/*.py"
|
||||
- "releasenotes/notes/*.yaml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
reno:
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Skip mandatory job
|
||||
run: echo "Skipped!"
|
||||
@@ -0,0 +1,66 @@
|
||||
# OpenSSF Scorecard
|
||||
#
|
||||
# Runs the OpenSSF Scorecard analyzer (https://github.com/ossf/scorecard) and
|
||||
# publishes results to GitHub's Security tab via the code-scanning SARIF API.
|
||||
#
|
||||
# Why: Scorecard gives haystack maintainers a single weekly signal on
|
||||
# supply-chain posture (branch protection, SAST coverage, dependency-update
|
||||
# tool, signed releases, etc.) without changing any existing CI behaviour.
|
||||
# It also lets downstream consumers verify the project's score on the
|
||||
# OpenSSF deps.dev / Security Insights dashboards.
|
||||
#
|
||||
# Permissions are scoped to the minimum required by the action:
|
||||
# - security-events: write -> upload SARIF
|
||||
# - id-token: write -> publish to the public OpenSSF API (badge)
|
||||
# - contents: read -> default repo read
|
||||
# - actions: read -> read CI config for checks
|
||||
#
|
||||
# Default branch is 'main'.
|
||||
|
||||
name: OpenSSF Scorecard
|
||||
|
||||
on:
|
||||
# Run weekly so results stay fresh, plus on branch-protection changes so the
|
||||
# branch-protection check is re-evaluated immediately in the Security tab.
|
||||
branch_protection_rule:
|
||||
schedule:
|
||||
- cron: '32 6 * * 1'
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
id-token: write
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run analysis
|
||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
# Publish results to the OpenSSF public REST API so the badge in
|
||||
# the README and deps.dev card stay up to date. Opt-in.
|
||||
publish_results: true
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
|
||||
- name: Upload to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -0,0 +1,130 @@
|
||||
name: Slow Integration Tests
|
||||
|
||||
# The workflow will always run, but the actual tests will only execute when:
|
||||
# - The workflow is triggered manually
|
||||
# - The workflow is scheduled
|
||||
# - The PR has the "run-slow-tests" label
|
||||
# - The push is to a release branch
|
||||
# - There are changes to relevant files.
|
||||
# Note: If no conditions are met, the workflow will complete successfully without running tests
|
||||
# to satisfy Branch Protection rules.
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HF_API_TOKEN: ${{ secrets.HUGGINGFACE_API_KEY }}
|
||||
PYTHON_VERSION: "3.10"
|
||||
HATCH_VERSION: "1.16.5"
|
||||
HAYSTACK_MPS_ENABLED: false
|
||||
HAYSTACK_XPU_ENABLED: false
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Activate this workflow manually
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
push:
|
||||
branches:
|
||||
# release branches have the form v1.9.x
|
||||
- "v[0-9].*[0-9].x"
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- labeled
|
||||
- unlabeled
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-if-changed:
|
||||
# This job checks if the relevant files have been changed.
|
||||
# We check for changes in the check-if-changed job instead of using paths/paths-ignore at workflow level.
|
||||
# This ensures the "Slow Integration Tests completed" job always runs, which is required by Branch Protection rules.
|
||||
name: Check if changed
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
pull-requests: read
|
||||
# Specifying outputs is not needed to make the job work, but only to comply with actionlint
|
||||
outputs:
|
||||
changes: ${{ steps.changes.outputs.changes }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for changed code
|
||||
id: changes
|
||||
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
|
||||
with:
|
||||
# List of Python files that trigger slow integration tests when modified
|
||||
filters: |
|
||||
changes:
|
||||
- "haystack/components/evaluators/sas_evaluator.py"
|
||||
- "haystack/components/generators/openai_dalle.py"
|
||||
- "haystack/components/preprocessors/embedding_based_document_splitter.py"
|
||||
- "haystack/components/retrievers/multi_query_embedding_retriever.py"
|
||||
|
||||
- "test/components/evaluators/test_sas_evaluator.py"
|
||||
- "test/components/generators/test_openai_dalle.py"
|
||||
- "test/components/preprocessors/test_embedding_based_document_splitter.py"
|
||||
- "test/components/retrievers/test_multi_query_embedding_retriever.py"
|
||||
|
||||
slow-integration-tests:
|
||||
name: Slow Tests / ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: check-if-changed
|
||||
timeout-minutes: 30
|
||||
# Run tests if: manual trigger, scheduled, PR has label, release branch, or relevant files changed
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event_name == 'schedule' ||
|
||||
(github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'run-slow-tests')) ||
|
||||
(github.event_name == 'push' && github.ref == 'refs/heads/v[0-9].*[0-9].x') ||
|
||||
(needs.check-if-changed.outputs.changes == 'true')
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
id: hatch
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
|
||||
- name: Run tests
|
||||
run: hatch run test:integration-only-slow
|
||||
|
||||
- name: Notify Slack on nightly failure
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
uses: deepset-ai/notify-slack-action@a65def0c8bf91d6520286ab34280151c76a5a008 # v1.1.0
|
||||
with:
|
||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL_NOTIFICATIONS }}
|
||||
|
||||
slow-integration-tests-completed:
|
||||
# This job always runs and succeeds if all tests succeed or are skipped. It is required by Branch Protection rules.
|
||||
name: Slow Integration Tests completed
|
||||
runs-on: ubuntu-slim
|
||||
if: ${{ always() && !cancelled() }}
|
||||
needs: slow-integration-tests
|
||||
|
||||
steps:
|
||||
- name: Mark tests as completed
|
||||
run: |
|
||||
if [ "${{ needs.slow-integration-tests.result }}" = "failure" ]; then
|
||||
echo "Slow Integration Tests failed!"
|
||||
exit 1
|
||||
else
|
||||
echo "Slow Integration Tests completed!"
|
||||
fi
|
||||
@@ -0,0 +1,21 @@
|
||||
name: 'Stalebot'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
makestale:
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
||||
with:
|
||||
any-of-labels: 'proposal,information-needed'
|
||||
stale-pr-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
|
||||
days-before-stale: 30
|
||||
days-before-close: 10
|
||||
@@ -0,0 +1,343 @@
|
||||
name: Tests
|
||||
|
||||
# The workflow will always run, but the actual tests will only execute when:
|
||||
# - The workflow is triggered manually.
|
||||
# - The push is to main or a release branch.
|
||||
# - There are changes to relevant files on a pull request.
|
||||
# Note: If no conditions are met, the workflow will complete successfully without running tests
|
||||
# to satisfy Branch Protection rules.
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Activate this workflow manually
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# release branches have the form v1.9.x
|
||||
- "v[0-9].*[0-9].x"
|
||||
# when we push, we do not need to satisfy Branch Protection rules, so we can ignore PRs that just change docs
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "docs-website/**"
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
CORE_AZURE_CS_ENDPOINT: ${{ secrets.CORE_AZURE_CS_ENDPOINT }}
|
||||
CORE_AZURE_CS_API_KEY: ${{ secrets.CORE_AZURE_CS_API_KEY }}
|
||||
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
|
||||
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
|
||||
HF_API_TOKEN: ${{ secrets.HUGGINGFACE_API_KEY }}
|
||||
PYTHON_VERSION: "3.10"
|
||||
HATCH_VERSION: "1.16.5"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-if-changed:
|
||||
# This job checks if the relevant files have been changed.
|
||||
# We check for changes in the check-if-changed job instead of using paths/paths-ignore at workflow level.
|
||||
# This ensures the "Mark tests as completed" job always runs, which is required by Branch Protection rules.
|
||||
name: Check if changed
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
pull-requests: read
|
||||
# Specifying outputs is not needed to make the job work, but only to comply with actionlint
|
||||
outputs:
|
||||
changes: ${{ steps.changes.outputs.changes }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check for changed code
|
||||
id: changes
|
||||
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
|
||||
with:
|
||||
filters: |
|
||||
changes:
|
||||
- "haystack/**/*.py"
|
||||
- "test/**/*.py"
|
||||
- "pyproject.toml"
|
||||
- ".github/utils/*.py"
|
||||
- "scripts/*.py"
|
||||
|
||||
format:
|
||||
needs: check-if-changed
|
||||
# Run tests if: manual trigger, push to main/release, or relevant files changed
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event_name == 'push' ||
|
||||
(needs.check-if-changed.outputs.changes == 'true')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
|
||||
- name: Ruff - check format and linting
|
||||
run: hatch run fmt-check
|
||||
|
||||
- name: Check presence of license header
|
||||
run: docker run --rm -v "$(pwd):/github/workspace" ghcr.io/korandoru/hawkeye:v6.5.1@sha256:0ebd72353053aa6d3f0cb44a6897786386347bd4636ffc9592f8e47c81ea2b9d check
|
||||
|
||||
check-imports:
|
||||
needs: format
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
|
||||
- name: Check imports
|
||||
run: hatch run python .github/utils/check_imports.py
|
||||
|
||||
unit-tests:
|
||||
name: Unit / ${{ matrix.os }}
|
||||
needs: format
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macos-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: true # needed for python-coverage-comment-action
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
id: hatch
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
echo "env=$(hatch env find test)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Run
|
||||
run: hatch run test:unit
|
||||
|
||||
- uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
id: cache
|
||||
if: matrix.os == 'macos-latest'
|
||||
with:
|
||||
path: ${{ steps.hatch.outputs.env }}
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
|
||||
# On PR: posts coverage comment (directly on same-repo PRs; via artifact for fork PRs).
|
||||
# On push to main: stores coverage baseline on the data branch.
|
||||
# We only upload coverage for ubuntu-latest, as handling multiple OSes adds complexity for little gain.
|
||||
- name: Coverage comment
|
||||
id: coverage_comment
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
uses: py-cov-action/python-coverage-comment-action@5d8df5979747514c914e1c5a12335a7cf9a2745f # v4.1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
MINIMUM_GREEN: 90
|
||||
MINIMUM_ORANGE: 60
|
||||
|
||||
- name: Upload coverage comment to be posted
|
||||
if: matrix.os == 'ubuntu-latest' && github.event_name == 'pull_request' && steps.coverage_comment.outputs.COMMENT_FILE_WRITTEN == 'true'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: python-coverage-comment-action
|
||||
path: python-coverage-comment-action.txt
|
||||
|
||||
mypy:
|
||||
needs: unit-tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# With the default value of 1, there are corner cases where tj-actions/changed-files
|
||||
# fails with a `no merge base` error
|
||||
fetch-depth: 0
|
||||
- name: Get changed files
|
||||
id: files
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: |
|
||||
**/*.py
|
||||
pyproject.toml
|
||||
files_ignore: |
|
||||
test/**
|
||||
.github/**
|
||||
scripts/**
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
if: steps.files.outputs.any_changed == 'true'
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
id: hatch
|
||||
if: steps.files.outputs.any_changed == 'true'
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
echo "env=$(hatch env find test)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Mypy
|
||||
if: steps.files.outputs.any_changed == 'true'
|
||||
run: |
|
||||
mkdir .mypy_cache
|
||||
hatch run test:types
|
||||
|
||||
integration-tests-linux:
|
||||
name: Integration / ubuntu-latest
|
||||
needs: unit-tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
id: hatch
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
echo "env=$(hatch env find test)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Run
|
||||
run: hatch run test:integration-only-fast
|
||||
|
||||
integration-tests-macos:
|
||||
name: Integration / macos-latest
|
||||
needs: unit-tests
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
HAYSTACK_MPS_ENABLED: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
id: hatch
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
echo "env=$(hatch env find test)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||
id: cache
|
||||
with:
|
||||
path: ${{ steps.hatch.outputs.env }}
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
|
||||
- name: Run
|
||||
run: hatch run test:integration-only-fast
|
||||
|
||||
integration-tests-windows:
|
||||
name: Integration / windows-latest
|
||||
needs: unit-tests
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
HAYSTACK_XPU_ENABLED: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Install Hatch
|
||||
id: hatch
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install hatch==${{ env.HATCH_VERSION }} --uploaded-prior-to=P1D
|
||||
echo "env=$(hatch env find test)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Run
|
||||
run: hatch run test:integration-only-fast
|
||||
|
||||
notify-slack-on-failure:
|
||||
if: failure() && github.ref_name == 'main'
|
||||
needs:
|
||||
- check-imports
|
||||
- mypy
|
||||
- integration-tests-linux
|
||||
- integration-tests-macos
|
||||
- integration-tests-windows
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: deepset-ai/notify-slack-action@a65def0c8bf91d6520286ab34280151c76a5a008 # v1.1.0
|
||||
with:
|
||||
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL_NOTIFICATIONS }}
|
||||
|
||||
tests-completed:
|
||||
# This job always runs and succeeds if all tests succeed or are skipped. It is required by Branch Protection rules.
|
||||
name: Mark tests as completed
|
||||
runs-on: ubuntu-slim
|
||||
if: ${{ always() && !cancelled() }}
|
||||
needs:
|
||||
- check-imports
|
||||
- mypy
|
||||
- integration-tests-linux
|
||||
- integration-tests-macos
|
||||
- integration-tests-windows
|
||||
|
||||
steps:
|
||||
- name: Mark tests as completed
|
||||
run: |
|
||||
if [ "${{ needs.check-imports.result }}" = "failure" ] ||
|
||||
[ "${{ needs.mypy.result }}" = "failure" ] ||
|
||||
[ "${{ needs.integration-tests-linux.result }}" = "failure" ] ||
|
||||
[ "${{ needs.integration-tests-macos.result }}" = "failure" ] ||
|
||||
[ "${{ needs.integration-tests-windows.result }}" = "failure" ]; then
|
||||
echo "Tests failed!"
|
||||
exit 1
|
||||
else
|
||||
echo "Tests completed!"
|
||||
fi
|
||||
@@ -0,0 +1,99 @@
|
||||
name: Update Platform Components Table
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: update-platform-components
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: "3.11"
|
||||
OUTPUT_FILE: docs-website/docs/overview/platform-components.mdx
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
steps:
|
||||
- name: Checkout deepset-ai/haystack
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
# Full history so git diff against HEAD works correctly
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout deepset-ai/haystack-core-integrations
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: deepset-ai/haystack-core-integrations
|
||||
path: haystack-core-integrations
|
||||
fetch-depth: 1
|
||||
sparse-checkout: integrations
|
||||
sparse-checkout-cone-mode: true
|
||||
|
||||
- name: Checkout platform repo (sparse — schema file only)
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: ${{ secrets.SCHEMA_REPO }}
|
||||
token: ${{ secrets.PLATFORM_REPO_PAT }}
|
||||
path: platform-repo
|
||||
sparse-checkout: |
|
||||
${{ secrets.SCHEMA_REPO_PATH }}
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "${{ env.PYTHON_VERSION }}"
|
||||
|
||||
- name: Generate platform components table
|
||||
run: |
|
||||
python scripts/generate_platform_components_table.py \
|
||||
--haystack-src . \
|
||||
--integrations-src ./haystack-core-integrations \
|
||||
--schema ./platform-repo/${{ secrets.SCHEMA_REPO_PATH }} \
|
||||
--output ./${{ env.OUTPUT_FILE }}
|
||||
|
||||
- name: Compute PR body
|
||||
id: pr_body
|
||||
run: |
|
||||
FILE="${{ env.OUTPUT_FILE }}"
|
||||
if git ls-files --error-unmatch "$FILE" 2>/dev/null; then
|
||||
ADDED=$(git diff HEAD -- "$FILE" | grep -cE '^\+\| `' || true)
|
||||
REMOVED=$(git diff HEAD -- "$FILE" | grep -cE '^\-\| `' || true)
|
||||
SUMMARY="Component rows changed: **+${ADDED}** added, **-${REMOVED}** removed."
|
||||
else
|
||||
SUMMARY="Initial generation of the platform components table."
|
||||
fi
|
||||
|
||||
{
|
||||
echo "body<<EOF"
|
||||
echo "## Platform Components Table Update"
|
||||
echo ""
|
||||
echo "${SUMMARY}"
|
||||
echo ""
|
||||
echo "_Auto-generated by the [update-platform-components](https://github.com/deepset-ai/haystack/actions/workflows/update-platform-components.yml) workflow._"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Open or update pull request
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.HAYSTACK_BOT_TOKEN }}
|
||||
commit-message: "chore: update platform components table"
|
||||
branch: chore/update-platform-components
|
||||
base: main
|
||||
title: "chore: update platform components table"
|
||||
add-paths: |
|
||||
${{ env.OUTPUT_FILE }}
|
||||
body: ${{ steps.pr_body.outputs.body }}
|
||||
labels: |
|
||||
documentation
|
||||
ignore-for-release-notes
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Github workflows linter
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint-workflows:
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
|
||||
with:
|
||||
go-version: ">=1.24.0"
|
||||
|
||||
- name: Install actionlint
|
||||
run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
|
||||
|
||||
- name: Run actionlint
|
||||
env:
|
||||
SHELLCHECK_OPTS: --exclude=SC2102
|
||||
run: actionlint
|
||||
Reference in New Issue
Block a user