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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,26 @@ jobs:
ls -la .coverage* 2>/dev/null || echo "no .coverage data files found"

- name: Combine coverage and enforce combined threshold
id: coverage-gate
run: make ci-tests-coverage-check

# Single deterministic Codecov upload: the combined report from ALL legs is
# sent once here, instead of each leg uploading separately. Codecov then
# shows one stable number (no mid-run partial-merge flapping) and this needs
# no hardcoded leg count — it auto-scales to any number of provider legs.
# if: always() so the report uploads even when the threshold gate fails
# above (the job still fails on the gate; Codecov still gets the data).
- name: Upload combined coverage to Codecov
if: always()
continue-on-error: true
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage-combined.xml
name: combined
# Overwrite any prior partial report for this commit with the full merge.
override_commit: ${{ github.event.pull_request.head.sha || github.sha }}

# Single stable required status check for branch protection. Replaces listing
# every individual job name (which drifts silently when jobs are renamed).
# Register "CI Passed" as the sole required check in branch protection.
Expand Down
15 changes: 6 additions & 9 deletions .github/workflows/reusable-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,12 @@ jobs:
coverage-*.xml
.coverage*

- name: Upload coverage to Codecov
if: inputs.upload-coverage && always()
continue-on-error: true
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage-${{ inputs.test-type }}${{ inputs.provider != '' && format('-{0}', inputs.provider) || '' }}.xml
flags: ${{ inputs.test-type }}${{ inputs.provider != '' && format('-{0}', inputs.provider) || '' }}
name: ${{ inputs.test-type }}${{ inputs.provider != '' && format('-{0}', inputs.provider) || '' }}-tests
# NOTE: per-leg coverage is NOT uploaded to Codecov here. The .coverage
# data files are uploaded as artifacts (above) and the coverage-combine
# fan-in job in ci-tests.yml merges them and uploads ONE combined report
# to Codecov. A single upload gives Codecov one deterministic number
# (no mid-run partial-merge flapping) and auto-scales to any number of
# provider legs without a hardcoded after_n_builds count.

- name: Upload test results to Codecov
# Gated on upload-coverage: legs without pytest (e.g. ui-smoke) produce
Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,36 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### BREAKING CHANGES

- **`Template.instance_type` renamed to `Template.machine_type`.**
The `instance_type` field on the `Template` domain model has been renamed
to `machine_type` to reflect provider-neutral terminology. The old name
is accepted as a write-only alias (via `AliasChoices`) for backward
compatibility with existing YAML / JSON configuration files, but it emits
a `DeprecationWarning` when used in Python code. Update template
definitions to use `machine_type` at your earliest convenience.

- **`Template.instance_profile` renamed to `Template.machine_role`.**
The `instance_profile` field has been renamed to `machine_role` for the
same provider-neutral reason. The old name is accepted as a write-only
alias with a `DeprecationWarning`, just like `instance_type` above.
Update template definitions to use `machine_role`.

- **`Template.provider_data` field removed.**
The `provider_data` catch-all dict has been deleted. Provider-specific
data is now carried on typed subclasses (`AWSTemplate`, `K8sTemplate`,
etc.). Code that reads or writes `template.provider_data` must be
migrated to the appropriate typed subclass field.

### Deprecated aliases

The following backward-compat aliases are available on the `Template` model
and will be removed in the next major release:

| Old name | New name | Notes |
|---|---|---|
| `instance_type` | `machine_type` | Accepted by `model_validate` and `__init__`; emits `DeprecationWarning` in Python |
| `instance_profile` | `machine_role` | Accepted by `model_validate` and `__init__`; emits `DeprecationWarning` in Python |

- **Daemon token file at `<work_dir>/server/orb-server.token`.**
When `orb server start` is invoked (daemon or `--foreground` mode), a
random bearer token is written to `<work_dir>/server/orb-server.token`
Expand Down
44 changes: 36 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,24 @@
</p>

<p align="center">
<a href="https://pypi.org/project/orb-py/"><img src="https://img.shields.io/pypi/v/orb-py" alt="PyPI Version"></a>
<a href="https://pypi.org/project/orb-py/"><img src="https://img.shields.io/pypi/pyversions/orb-py" alt="Python Versions"></a>
<a href="LICENSE"><img src="https://img.shields.io/github/license/finos/open-resource-broker" alt="License"></a>
<a href="https://pypi.org/project/orb-py/"><img src="https://img.shields.io/pypi/v/orb-py" alt="PyPI Version"></a>
<a href="https://github.com/finos/open-resource-broker/releases"><img src="https://img.shields.io/github/v/release/finos/open-resource-broker" alt="Latest Release"></a>
<a href="https://pypi.org/project/orb-py/"><img src="https://img.shields.io/pypi/pyversions/orb-py" alt="Python Versions"></a>
<a href="https://deepwiki.com/finos/open-resource-broker"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
</p>

<p align="center">
<br>
<a href="https://github.com/finos/open-resource-broker/actions/workflows/ci-tests.yml"><img src="https://github.com/finos/open-resource-broker/actions/workflows/ci-tests.yml/badge.svg" alt="Unit Tests"></a>
<a href="https://github.com/finos/open-resource-broker/actions/workflows/ci-quality.yml"><img src="https://github.com/finos/open-resource-broker/actions/workflows/ci-quality.yml/badge.svg" alt="Quality Checks"></a>
<a href="https://github.com/finos/open-resource-broker/actions/workflows/security-code.yml"><img src="https://github.com/finos/open-resource-broker/actions/workflows/security-code.yml/badge.svg" alt="Security Scanning"></a>
<a href="https://codecov.io/gh/finos/open-resource-broker"><img src="https://codecov.io/gh/finos/open-resource-broker/graph/badge.svg" alt="Coverage"></a>
<a href="https://github.com/finos/open-resource-broker/actions/workflows/docs.yml"><img src="https://github.com/finos/open-resource-broker/actions/workflows/docs.yml/badge.svg" alt="Documentation"></a>
<br>
<a href="https://codecov.io/gh/finos/open-resource-broker"><img src="https://codecov.io/gh/finos/open-resource-broker/graph/badge.svg" alt="Coverage"></a>
<a href="https://scorecard.dev/viewer/?uri=github.com/finos/open-resource-broker"><img src="https://api.securityscorecards.dev/projects/github.com/finos/open-resource-broker/badge" alt="OpenSSF Scorecard"></a>
</p>

---

ORB is a unified API for orchestrating and provisioning compute capacity programmatically. Define what you need in a template, request it, track it, return it — through a CLI, REST API, Python SDK, or MCP server.
Open Resource Broker (ORB) is a unified API for orchestrating and provisioning compute capacity programmatically. Define what you need in a template, request it, track it, return it — through a CLI, REST API, Python SDK, or MCP server.

Built for AWS today (EC2, Auto Scaling Groups, SpotFleet, EC2Fleet), with an extensible provider system for adding new cloud backends.

Expand Down Expand Up @@ -192,6 +192,31 @@ See the [AWS Provider Guide](docs/root/user_guide/configuration.md) for required

</details>

<details>
<summary>Kubernetes Provider Setup</summary>

ORB uses the standard Kubernetes client credential chain — any context that works with `kubectl` works with ORB. Running in-cluster, it auto-detects the mounted service account; running outside, it reads your `KUBECONFIG` and current context.

```bash
# Verify your context is active
kubectl config current-context
```

**Supported credential methods:** `KUBECONFIG` contexts, the default `~/.kube/config`, and in-cluster service accounts (auto-detected). See [provider discovery](docs/root/providers/k8s/discovery.md) for the detection order.

### Supported resource types

| Type | Description |
|---|---|
| `Pod` | Single-pod provisioning |
| `Deployment` | Replica-managed stateless workloads |
| `StatefulSet` | Stable-identity stateful workloads |
| `Job` | Run-to-completion batch workloads |

Install with `pip install "orb-py[k8s]"`. Minimum RBAC is in [`docs/root/providers/k8s/rbac.yaml`](docs/root/providers/k8s/rbac.yaml); see the [Kubernetes Provider Guide](docs/root/providers/k8s/index.md) for full setup.

</details>

## Interfaces

ORB provides four ways to interact with your infrastructure.
Expand Down Expand Up @@ -377,7 +402,8 @@ curl http://localhost:8000/health

</details>

## Symphony HostFactory on Kubernetes (legacy)
<details>
<summary>Symphony HostFactory on Kubernetes (legacy)</summary>

The `k8s-legacy` module is a Symphony HostFactory custom provider plugin for Kubernetes, predating the modern multi-cloud ORB architecture. It is now bundled with `orb-py` as an optional install extra rather than as a separate PyPI package.

Expand All @@ -398,6 +424,8 @@ The plugin is in maintenance mode. A modern Kubernetes provider with native ORB
- **Upgrading from `open-resource-broker`?** See the [migration guide](docs/root/operational/from-open-resource-broker.md).
- **Deploying the Symphony HF plugin?** See the [k8s-legacy deployment guide](k8s-legacy/README.md).

</details>

## Project

Architecture, development, and documentation.
Expand Down
12 changes: 12 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Coverage is uploaded to Codecov exactly ONCE, from the ci-tests
# coverage-combine job, which merges every leg's .coverage data first. A single
# upload gives Codecov one deterministic number (no mid-run partial-merge
# flapping) and needs no hardcoded leg count — it auto-scales to any number of
# provider legs.
coverage:
status:
project:
Expand All @@ -22,7 +27,14 @@ flags:
- src/orb/
carryforward: true

# Kept in sync with [tool.coverage.run] omit in pyproject.toml so Codecov's
# view matches the combined report we upload.
ignore:
- "tests/"
- "docs/"
- "scripts/"
- "src/orb/k8s_legacy/**" # own separate test suite; runs outside the main legs
- "**/tests/**" # test files under src/ are not production source
- "src/orb/__main__.py" # thin process entrypoints — not unit-tested
- "src/orb/run.py"
- "src/orb/interface/server_daemon.py"
36 changes: 7 additions & 29 deletions config/default_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,6 @@
"audit_logs": "audit_logs",
"metrics_logs": "metrics_logs"
},
"fleet_types": {
"instant": "instant",
"request": "request",
"maintain": "maintain"
},
"price_types": {
"ondemand": "ondemand",
"spot": "spot",
"heterogeneous": "heterogeneous"
},
"statuses": {
"request": {
"pending": "pending",
Expand Down Expand Up @@ -80,22 +70,14 @@
}
},
"patterns": {
"ec2_instance": "^i-[a-f0-9]+$",
"spot_fleet": "^sfr-[a-f0-9]+$",
"ec2_fleet": "^fleet-[a-f0-9]+$",
"asg": "^[a-zA-Z0-9_-]+$",
"instance_type": "^[a-z][0-9][a-z]?\\.[a-z0-9]+$",
"region": "^[a-z]{2}-[a-z]+-\\d$",
"request_id": "^(req|ret)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
"request_id": "^(req|ret)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
"tag_key": "^[\\w\\s+=.@-]+$",
"cidr_block": "^(\\d{1,3}\\.){3}\\d{1,3}/\\d{1,2}$"
},
"prefixes": {
"default": "",
"request": "req-",
"return_prefix": "ret-",
"launch_template": "",
"instance": "",
"fleet": "",
"asg": "",
"tag": ""
}
},
Expand Down Expand Up @@ -214,14 +196,15 @@
},
"server": {
"enabled": false,
"host": "0.0.0.0",
"host": "127.0.0.1",
"port": 8000,
"workers": 1,
"reload": false,
"docs_enabled": true,
"docs_url": "/docs",
"redoc_url": "/redoc",
"openapi_url": "/openapi.json",
"trusted_hosts": ["localhost", "127.0.0.1", "::1", "testserver"],
"auth": {
"enabled": false,
"strategy": "none",
Expand All @@ -234,7 +217,7 @@
"cors": {
"enabled": true,
"origins": [
"*"
"http://localhost:8000"
],
"methods": [
"GET",
Expand Down Expand Up @@ -270,12 +253,7 @@
"default": "",
"request": "req-",
"return_prefix": "ret-",
"launch_template": "",
"instance": "",
"fleet": "",
"spot_fleet": "",
"asg": "",
"tag": ""
}
}
}
}
21 changes: 11 additions & 10 deletions dev-tools/package/generate_pyproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,20 @@ def update_pyproject_selective(pyproject_path: Path) -> None:
continue

elif line.startswith("[tool.coverage.run]"):
# Replace coverage source with package_root from .project.yml
new_lines.extend(
[
"[tool.coverage.run]",
f'source = ["{package_root}"]',
"branch = true",
"",
]
)
# Skip existing coverage.run section
# Re-template only source (from package_root) + branch; preserve every
# other hand-authored key in this section (e.g. omit) instead of
# dropping it — the generator manages source, not the whole policy.
i += 1
preserved = []
while i < len(lines) and not lines[i].strip().startswith("["):
existing = lines[i].strip()
if not existing.startswith("source ") and not existing.startswith("branch "):
preserved.append(lines[i])
i += 1
new_lines.append("[tool.coverage.run]")
new_lines.append(f'source = ["{package_root}"]')
new_lines.append("branch = true")
new_lines.extend(preserved)
continue

else:
Expand Down
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ nav:
- Performance Optimization: developer_guide/performance-optimization.md
- Development Setup: developer_guide/development.md
- BaseSetting Providers: developer_guide/basesettings-providers.md
- Deprecating Fields & APIs: developer_guide/deprecation.md
- Changelog Management: developer_guide/changelog_management.md
- Releases: developer_guide/releases.md
- Testing: developer_guide/testing.md
Expand Down
12 changes: 6 additions & 6 deletions docs/root/cli/template-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Templates can be defined in JSON or YAML format:
"name": "My AWS Template",
"provider_api": "aws",
"image_id": "ami-12345678",
"instance_type": "t3.medium",
"machine_type": "t3.medium",
"key_name": "my-keypair",
"security_group_ids": ["sg-12345678"],
"subnet_ids": ["subnet-12345678"],
Expand All @@ -94,7 +94,7 @@ template_id: my-aws-template
name: My AWS Template
provider_api: aws
image_id: ami-12345678
instance_type: t3.medium
machine_type: t3.medium
key_name: my-keypair
security_group_ids:
- sg-12345678
Expand Down Expand Up @@ -179,7 +179,7 @@ $ orb templates list
"name": "Basic AWS Template",
"provider_api": "aws",
"image_id": "ami-12345678",
"instance_type": "t3.medium"
"machine_type": "t3.medium"
}
],
"total_count": 1,
Expand All @@ -195,7 +195,7 @@ $ orb templates show aws-basic
"name": "Basic AWS Template",
"provider_api": "aws",
"image_id": "ami-12345678",
"instance_type": "t3.medium",
"machine_type": "t3.medium",
"key_name": "my-keypair",
"security_group_ids": ["sg-12345678"],
"subnet_ids": ["subnet-12345678"],
Expand Down Expand Up @@ -248,7 +248,7 @@ $ orb templates validate --file invalid-template.json
"valid": false,
"validation_errors": [
"Missing required field: template_id",
"Invalid instance_type: must be valid EC2 instance type"
"Invalid machine_type: must be a valid instance type"
],
"validation_warnings": [
"No tags specified: recommended for resource management"
Expand Down Expand Up @@ -295,7 +295,7 @@ $ orb templates refresh
```bash
$ orb templates list --format table
+---------------+--------------------+------------+---------------+-------------+
│ Template ID │ Name │ Provider API │ Image ID │ Instance Type │
│ Template ID │ Name │ Provider API │ Image ID │ Machine Type
+---------------+--------------------+------------+---------------+-------------+
│ aws-basic │ Basic AWS Template │ aws │ ami-12345678 │ t3.medium │
│ aws-spot │ Spot Instance Temp │ aws │ ami-87654321 │ t3.large │
Expand Down
Loading
Loading