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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/skills/bump/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ Steps:
5. Remind the user: version bumps must be committed before opening a PR, and the PR title must follow `[Scope] description` format.
6. Update the MLRun CE version under Version Matrix in `charts/mlrun-ce/README.md`.

`scripts/install.sh` needs no edit β€” it reads its version from `Chart.yaml` at runtime, so bumping the chart bumps the installer too.

If no argument is given, show the current version and list the three options with the resulting version for each, then ask which to apply.
161 changes: 161 additions & 0 deletions .claude/skills/run-tests/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
name: run-tests
description: >-
Run and extend the bats test suite for the CE installer (scripts/install.sh).
Use when a developer asks to run installer tests, check coverage, add a new
test, or verify a change to scripts/install.sh didn't break existing behaviour.
---

# CE installer test suite

Tests live in `tests/install_tests.bats` and use
[bats-core](https://github.com/bats-core/bats-core). They source
`scripts/install.sh` without executing it (via `INSTALL_SH_SOURCE_ONLY=true`) and
stub out external binaries so no live cluster is needed.

These cover the installer only. The chart's own tests are the other scripts in
`tests/` (`helm-template-test.sh`, `kind-test.sh`) plus `make helm-lint` β€” all
unrelated to this suite.

## Prerequisites

```bash
brew install bats-core # macOS; already installed if tests have been run before
```

## Run the full suite

From the repo root:

```bash
make installer-test
# or directly:
bats tests/install_tests.bats
```

Expected output: `1..118` followed by `ok N <test-name>` for every test.

Keep the count in this file in sync when you add tests β€” it's the quickest way to
notice a test silently failing to register.

## Run a single test by name

```bash
bats --filter "CI=true sets NON_INTERACTIVE" tests/install_tests.bats
```

## Run with verbose output

```bash
bats --verbose-run tests/install_tests.bats
```

## How tests source the script safely

Every test opens with:

```bash
run bash -c "
INSTALL_SH_SOURCE_ONLY=true source '$SCRIPT'
...
"
```

`SCRIPT` is defined once at the top of the file as
`"$BATS_TEST_DIRNAME/../scripts/install.sh"`, so the suite works regardless of the
directory bats is invoked from.

`INSTALL_SH_SOURCE_ONLY=true` skips the `main "$@"` call at the bottom of
`install.sh` (guarded by `[[ "${INSTALL_SH_SOURCE_ONLY:-}" == "true" ]] || main "$@"`),
so sourcing only defines functions and global variables β€” no cluster, no
prompts, no helm calls.

External binaries (`helm`, `kubectl`, `docker`) are **not** needed for
flag-parsing or `prompt_or_env` tests. For tests that exercise `main()`, stub
every function it calls:

```bash
check_requirements() { :; }
ensure_namespace() { :; }
create_registry_secret() { :; }
verify_existing_registry_secret(){ :; }
gather_install_params() { :; }
run_validators() { echo "run_validators called"; }
helm_install() { echo "sentinel output"; }
```

Tests that exercise the validators individually stub `kubectl`/`helm`/`docker` as
shell functions instead, echoing whatever the check parses (a `kubeletVersion`,
a `helm version --short` string, an allocatable quantity, and so on).

## Current coverage β€” 118 tests

| Phase / area | Tests |
|--------------|-------|
| **Commands** β€” `parse_command` | `install`/`uninstall` consume the verb and keep their flags, `version`/`help` print and exit 0, a leading flag or no arguments at all still means install (the empty-array case that trips `set -u` on bash 3.2), an unknown word exits 1 instead of installing |
| **Output** β€” color handling | no escape sequences when stdout isn't a TTY; `NO_COLOR` honored |
| **Versioning** β€” `installer_version` / `--version` | reads the version from the chart beside the script, tracks it when the chart version changes, reports `unknown` when run standalone, `-v` short form |
| **Phase 1** β€” `--ce-version` parsing | stores value, rejects missing arg, respects env var, defaults empty |
| **Phase 1** β€” `--dry-run` / `--non-interactive` | each sets its var, each defaults false, flag consumed cleanly |
| **Phase 1** β€” `prompt_or_env` non-interactive | returns default, exits 1 with no default, env var wins over default |
| **Phase 1** β€” CI auto-detect | `CI=true` sets `NON_INTERACTIVE`; unset CI leaves it false |
| **Phase 2** β€” `--chart-path` / `resolve_chart_source` | flag parsing (missing arg, flag-looking value), missing dir, missing `Chart.yaml`, `CHART_REF` in both path and published-repo mode, `--ce-version` ignored in path mode |
| **Phase 3** β€” `--config` / `load_config` | flag parsing, no-op when unset, missing file, missing `yq`, registry field parsing, config value as interactive prompt default, password key warned+ignored, `chartPath` required when `kind: path`, all missing required fields listed together in one pass, `--skip-secret`/`--local-registry` relaxations, never overriding flag/env-set values |
| **Phase 3** β€” `-f` + `--config` composition | secret still created when both are passed, `-f` alone still skips it (back-compat), registry `--set`s present with both and absent in pure `-f`-only mode |
| **Phase 3** β€” versions / components / otel | `installer.versions.*` β†’ image-tag `--set`s, `components.*` β†’ `DISABLE_*` (never re-enabling one set by flag), `otel.*` β†’ the 4 `ENABLE_OTEL_*` opt-ins, `--enable-otel [off\|collector\|full]` modes + invalid mode |
| **Phase 3** β€” registry secret / password | `REGISTRY_PASSWORD_FILE` read, env password wins over it, missing file exits 1, file satisfies the non-interactive password requirement, `verify_existing_registry_secret` present/absent |
| **Phase 3** β€” `KUBE_CONTEXT` | wrapper functions inject `--context`/`--kube-context`; `resolve_external_host` skips the docker-desktop/minikube heuristics when set, keeps them when unset, falls back to `localhost` when nothing matches |
| **Phase 4** β€” validators | `--skip-validators` flag and `main()` honoring it; Helm version and StorageClass blocking failures and passes; k8s version reported but never blocking; ingress-controller, registry-auth, NodePort and node-capacity warnings; bare-byte ephemeral-storage parsing; `MIN_HELM_VERSION` raising the floor, `MIN_K8S_VERSION` warning without blocking, empty default accepted and a malformed value rejected at load time; `run_validators` aggregating multiple blocking failures into one `exit 1` |

## Adding a new test

1. Open `tests/install_tests.bats`.
2. Add a `@test` block after the relevant section comment.
3. Follow the sourcing pattern above β€” `INSTALL_SH_SOURCE_ONLY=true source '$SCRIPT'`
inside a `run bash -c "..."` block.
4. Assert with standard bats: `[ "$status" -eq 0 ]`, `[ "$output" = "..." ]`,
`[[ "$output" == *"substring"* ]]`.
5. Run `bats tests/install_tests.bats` to confirm green.

> **Green on macOS is not green on CI.** Under macOS's system bash (3.2), a
> failed assertion that isn't the *last* statement of a `@test` is silently
> swallowed and the test still prints `ok`; CI runs bash 5, where it fails.
> After writing a test that stubs external commands, run its inner `bash -c`
> body standalone once and eyeball the output, or `brew install bash` so local
> runs behave like CI. Note that stubs of `command` must account for the
> `kubectl`/`helm` wrappers injecting `--context`/`--kube-context` before the
> real arguments.

### Minimal test template

```bash
@test "description of what is being tested" {
run bash -c "
INSTALL_SH_SOURCE_ONLY=true source '$SCRIPT'
# exercise the function or flag
parse_args --your-flag
echo \"\$YOUR_VAR\"
"
[ "$status" -eq 0 ]
[ "$output" = "expected" ]
}
```

## Key scripts/install.sh pointers

| Symbol | Location | Notes |
|--------|----------|-------|
| Global vars | lines 37-68 | All flags and env vars initialised here |
| `kubectl()` / `helm()` wrappers | lines 82-83 | Inject `KUBE_CONTEXT` into every call |
| `prompt_or_env()` | ~line 377 | Handles interactive/non-interactive/env-var precedence |
| `load_config()` | ~line 411 | Reads the `installer:` block of a `ce-config.yaml` |
| `resolve_external_host()` | ~line 602 | `EXTERNAL_HOST_ADDRESS` autodetect fallback chain |
| `resolve_chart_source()` | ~line 778 | Published-repo vs `--chart-path` mode |
| `helm_install()` | ~line 803 | Builds `extra_set_flags` and runs helm |
| `parse_args()` | ~line 934 | Flag β†’ variable mapping; add new flags here |
| `run_validators()` | ~line 1300 | Pre-install check dispatcher (blocking checks `return 1`) |
| `main()` | ~line 1319 | Orchestration; CI auto-detect lives here |
| Source guard | last line | `[[ "${INSTALL_SH_SOURCE_ONLY:-}" == "true" ]] \|\| main "$@"` |

These line numbers drift with every change to `install.sh` β€” prefer grepping for
the function name over trusting them.
85 changes: 85 additions & 0 deletions .github/workflows/installer-ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Installer CI

# Runs on every PR, not just those touching scripts/**. The unit tests are
# hermetic and take about a minute, and always running them means the suite
# can't sit broken unnoticed until the next installer change.
#
# Kept as its own workflow rather than a job in ci.yaml so it reports as an
# independent status check, matching this repo's one-workflow-per-concern layout.
on:
pull_request:
branches:
- development
- "[0-9]+.[0-9]+.x"
workflow_dispatch:

permissions:
contents: read

env:
BATS_VERSION: v1.13.0

jobs:
lint-and-test:
name: Lint and unit-test install.sh
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

# shellcheck and yq (mikefarah) ship with the ubuntu-latest runner image.
# yq is needed for real: the load_config tests parse actual YAML.
- name: Show tool versions
run: |
shellcheck --version
yq --version

- name: Lint install.sh
run: make installer-lint

- name: Install bats
run: |
git clone --depth 1 --branch "${BATS_VERSION}" \
https://github.com/bats-core/bats-core.git /tmp/bats-core
sudo /tmp/bats-core/install.sh /usr/local
bats --version

- name: Run installer unit tests
run: make installer-test

kind-install:
name: End-to-end install on kind
# Dispatch-only for the same reason ci.yaml's `test:` job is commented out:
# pulling the full MLRun CE image set takes too long for every PR.
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

# No node_image/kubectl_version pin: the installer enforces no Kubernetes floor, so
# the action's own default node image (matched to the kind release it bundles) is
# the safest choice.
- name: Set up Kubernetes cluster
uses: helm/kind-action@v1.10.0
with:
config: ./.github/assets/kind.yaml
wait: 180s

- name: Set up Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1

- name: Install MLRun CE from the in-repo chart
# --local-registry deploys a registry in-cluster and derives the pull
# secret from it, so no external registry credentials are needed.
run: |
./scripts/install.sh \
--chart-path ./charts/mlrun-ce \
--local-registry \
--non-interactive

- name: Dump cluster state on failure
if: failure()
run: |
kubectl get pods -A -o wide
kubectl get events -A --sort-by=.lastTimestamp | tail -50
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ otlp-pro/

# Claude Code local settings (machine-specific, not for commit)
.claude/settings.local.json

# Filled-in installer config (copied from scripts/ce-config.yaml.example).
# Unanchored, so it matches at any depth; ce-config.yaml.example is unaffected.
ce-config.yaml
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

This is a Helm umbrella chart repository for **MLRun Community Edition (CE)** β€” an open-source MLOps stack. The main chart lives at `charts/mlrun-ce/` and bundles: Nuclio, MLRun, Jupyter, MPI Operator, SeaweedFS (S3-compatible storage), Spark Operator, Kubeflow Pipelines, Prometheus stack, TimescaleDB, and Strimzi Kafka Operator.

The repo also ships `scripts/install.sh`, a bash installer that wraps `helm install` for this chart. It installs the **published** chart by default and this repo's chart only when given `--chart-path ./charts/mlrun-ce`. Its own conventions, phase history and bug log live in [`scripts/AGENTS.md`](scripts/AGENTS.md) β€” read that when working under `scripts/`; this file covers the chart.

## Commands to lint, package, and manage the chart:

```bash
Expand Down
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@
|---|---|---|
| helm | 3.6 | Chart rendering, linting, install |
| kubectl | 1.24 | Cluster interaction |
| bats-core | 1.5 | Only for `make installer-test` (the `scripts/install.sh` unit tests) |
| shellcheck | any | Only for `make installer-lint` |

For Kubernetes storage class setup and cluster prerequisites, see [charts/mlrun-ce/README.md](charts/mlrun-ce/README.md#prerequisites).

`scripts/install.sh` enforces the same helm 3.6 floor at install time and imposes no
Kubernetes floor, so a cluster you can develop against is one you can install against β€” see
[scripts/docs/configuration.md](scripts/docs/configuration.md#version-floors).

## First-Time Setup

```bash
Expand Down
28 changes: 28 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,34 @@ tests: ## Run tests
package: ## Package the application
@./tests/package.sh

.PHONY: installer-test
installer-test: ## Run the scripts/install.sh unit tests (requires bats-core)
@bats tests/install_tests.bats

.PHONY: installer-lint
installer-lint: ## Syntax-check and shellcheck scripts/install.sh
@bash -n scripts/install.sh
@shellcheck scripts/install.sh

# Symlink rather than copy, so the command tracks the working tree and can still find the
# chart next to it (a copy has no chart, and reports its version as unknown).
INSTALLER_BIN_DIR ?= $(HOME)/.local/bin

.PHONY: installer-link
installer-link: ## Put mlrun-ce-installer on PATH, pointing at this checkout
@mkdir -p "$(INSTALLER_BIN_DIR)"
@ln -sf "$(CURDIR)/scripts/install.sh" "$(INSTALLER_BIN_DIR)/mlrun-ce-installer"
@echo "linked $(INSTALLER_BIN_DIR)/mlrun-ce-installer -> $(CURDIR)/scripts/install.sh"
@case ":$$PATH:" in \
*":$(INSTALLER_BIN_DIR):"*) ;; \
*) echo "note: $(INSTALLER_BIN_DIR) is not on PATH β€” add it, or set INSTALLER_BIN_DIR" ;; \
esac

.PHONY: installer-unlink
installer-unlink: ## Remove the mlrun-ce-installer symlink
@rm -f "$(INSTALLER_BIN_DIR)/mlrun-ce-installer"
@echo "removed $(INSTALLER_BIN_DIR)/mlrun-ce-installer"

.PHONY: helm-lint
helm-lint: helm-repo-add ## Lint Helm Chart
@helm lint charts/mlrun-ce
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ The Open source MLRun CE chart includes the following stack:
## Installation

Refer to the installation instructions in the [README](charts/mlrun-ce/README.md) of the `mlrun-ce` chart.

For a scripted install, [`scripts/install.sh`](scripts/README.md) wraps those steps β€” registry
secret, pre-install validation and `helm install` β€” behind a single command.
2 changes: 1 addition & 1 deletion charts/mlrun-ce/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apiVersion: v1
name: mlrun-ce
version: 0.12.0-rc.11
version: 0.12.0-rc.12
appVersion: 1.12.0-rc25
description: MLRun Open Source Stack
home: https://iguazio.com
Expand Down
6 changes: 6 additions & 0 deletions charts/mlrun-ce/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ The Open source MLRun ce chart includes the following stack:

## Installing the Chart

> **Scripted alternative:** [`scripts/install.sh`](../../scripts/README.md) wraps everything
> below β€” namespace creation, the registry secret, pre-install validation and the `helm
> install` itself β€” behind one command, and can read its settings from a `ce-config.yaml`
> for repeatable installs. It installs the published chart by default. The manual steps
> below remain fully supported.

Create a namespace for the deployed components:
```bash
kubectl create namespace mlrun
Expand Down
Loading
Loading