Conversation
…plate - Swap -i for --default-index and --trusted-host for --allow-insecure-host, both deprecated or undocumented aliases, in a new shared PypiUrl.build_args - Honor UV_ALLOW_INSECURE_HOST, keeping PIP_ENABLE_TRUSTED_HOST as a deprecated alias so existing deployments keep working - Drop the duplicated index arg building in PythonPackageModel.install in favor of PypiUrl.build_args - Add openc3/templates/plugin/pyproject.toml plus lib/.gitkeep, both generated only for --python plugins and listed in the gemspec so COSMOS can see them - Replace the demo plugin's setuptools [build-system] shim with package = false and unpin its dev tooling Co-Authored-By: Claude Opus 5 (1M context)
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3896 +/- ##
==========================================
+ Coverage 80.13% 80.15% +0.02%
==========================================
Files 901 901
Lines 68370 68381 +11
Branches 2699 2646 -53
==========================================
+ Hits 54789 54813 +24
+ Misses 12913 12904 -9
+ Partials 668 664 -4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Add a templateNoLock criterion for text:S8565 on openc3/templates/plugin/pyproject.toml, which ships no uv.lock on purpose so generated plugins resolve current versions - Add the trailing newline the file was missing Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
…tall argv PythonPackageModel.install now builds its index arguments with PypiUrl.build_args, which emits uv's --allow-insecure-host rather than pip's --trusted-host alias. Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- Describe in plugins.md Phase 2 which index plugin Python dependencies resolve against, and that a non-default one suppresses a plugin's own [tool.uv].index and [tool.uv].sources - Note that a plugin shipping a uv.lock installs via uv sync --frozen and still downloads from whatever index its author locked against - Carry the same point into the environment.md PYPI_URL row, which until now only separated build-time from run-time Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- Add --no-sources alongside --no-config on the uv commands that resolve: --no-config stops uv searching a plugin's [tool.uv].index, but a [tool.uv].sources pin survives it and still resolves elsewhere - Keep it off the uv sync --frozen call, which uv rejects it on and which resolves nothing anyway - Make cd into VENV_BASE fatal so a failure cannot sync against whatever pyproject.toml the working directory holds - Add UVINSTALL_VENV_ROOT so the spec suite can drive uvinstall against a temporary directory - Quote the echoed argv with $* in pipinstall and pipuninstall, which ShellCheck rejects as SC2145 Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- Add --no-config to PypiUrl.build_args for any index other than the public default, so a plugin's own [tool.uv].index table cannot win the search over the index the operator configured - Cover the new flag in pypi_url_spec in both directions, and assert in plugin_model_spec that a configured pypi_url reaches the uvinstall argv verbatim rather than matching it with anything - Add install_scripts_spec, which drives the real uvinstall and pipinstall against a stub uv on PATH to pin where --no-sources is and is not added - Add uv_index_isolation_spec, which asserts uv's own precedence rules against a real uv so an upgrade that changes them fails here; it uses .invalid hosts to stay offline and skips when uv is absent - Assert that a plugin with no python dependencies never invokes uv Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
uvinstall, pipinstall and pipuninstall are /bin/sh scripts invoked by name as commands, so the *.sh glob never matched them and they had gone unchecked. Hoist the pathspecs into SHELL_PATHSPECS so the gate and the advisory step cannot drift apart, and add the same path to the trigger. Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- Replace the claim that a [tool.uv].sources pin is not overridden by the Admin Console setting, in both the plugin template and the demo plugin: a designated index now suppresses the whole table via --no-sources - Tell authors a dependency published only to their own index fails to install against such an index, and that a committed uv.lock sidesteps the whole question - Document the same behavior under PYPI_URL in .env Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
SHELL_PATHSPECS had to be unquoted to word split, which let the shell glob '*.sh' against the working directory first. It matched only the repository root, so the gate checked 4 files instead of 25 and still passed. Inline the pathspecs quoted in both steps and drop the variable that forced the unquoted expansion. Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- Add capture_spawn, which stubs ProcessManager and returns the argv and keyword arguments of the one spawn, so each .install and .destroy example is left with the assertion it is actually making - Add stub_plugin_venvs, replacing the File.directory? / Dir.glob trio that .names and .trees each repeated per example, with a nil value for a plugin directory lacking a .venv - Fold the throwaway package file into install_spawn, since every .install example wrote the same fixture and never read it back Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- Add gem_spec_double, replacing the eight-field gemspec double that 11 examples built by hand, so a call site names only the two fields that decide needs_dependencies and img_path - Add stub_s3_client for the Aws::S3::Client null object those same examples each set up - Add expect_tool_and_target_deploy for the ToolModel and TargetModel deploy pair, which varies only in the variables hash - Add unmigrated_plugin, stub_migration_gem and stub_default_pypi_url, so each migrate_to_uv! example stops rebuilding the model, the absent .uv_managed marker and the pypi_url fallback Refs #3724 Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Refs #3724 Co-Authored-By: Claude Opus 5.5 (1M context) noreply@anthropic.com
…t.py Refs #3724 Co-Authored-By: Claude Opus 5.5 (1M context) noreply@anthropic.com
- Substitute lib/PACKAGE with a package_name derived from the plugin name so a module can't collide with one from another plugin on sys.path - Add a uv section to the generated README.md for python plugins - Ignore .venv/, pycache/, .pytest_cache/ and .ruff_cache/ in the generated .gitignore for python plugins - Reject pycache files from the python plugin gemspec's s.files - Set pytest pythonpath = ["lib"] in the pyproject.toml template Refs #3724 Co-Authored-By: Claude Opus 5.5 (1M context) noreply@anthropic.com
Refs #3724 Co-Authored-By: Claude Opus 5.5 (1M context) noreply@anthropic.com
- Explain in the plugin README that the plugin isn't installed into .venv, so code outside pytest and ty needs PYTHONPATH=lib to import the package - Tag the README's shell code fences as sh Refs #3724 Co-Authored-By: Claude Opus 5.5 (1M context) noreply@anthropic.com
- Pass --pull to the openc3-ruby, buckets, redis, tsdb and traefik UBI builds so a mutable Iron Bank tag isn't served from a stale local copy - Add OPENC3_UBI_NO_PULL=1 to skip the pull when bases were loaded with docker load - Bump OPENC3_UBI_TAG in .env from 9.6 to 9.8 Co-Authored-By: Claude Opus 5.5 (1M context) noreply@anthropic.com
|
calmonroe
left a comment
There was a problem hiding this comment.
Nice! I love all the new spec files
jmthomas
left a comment
There was a problem hiding this comment.
Mostly minor issues. Are we close to converging on the uv / pypi updates?
|
|
||
| Dependencies are resolved against the `pypi_url` Admin Console setting, falling back to the `PYPI_URL` environment variable and then to public PyPI. When that setting names an index other than `https://pypi.org`, COSMOS treats it as authoritative and passes uv `--no-config --no-sources`, so a plugin cannot resolve around it using an index declared in its own `[tool.uv].index` table or a `[tool.uv].sources` pin. A plugin depending on a package published only to its author's private index therefore fails to install unless your index serves that package too. | ||
|
|
||
| This applies only to the paths where uv resolves — `requirements.txt` plugins, and `pyproject.toml` plugins with no `uv.lock`. A plugin shipping a `uv.lock` installs with `uv sync --frozen`, which reuses the registry and wheel URLs already recorded in that lock and never consults an index, so a locked plugin still downloads from whatever index its author locked against. |
There was a problem hiding this comment.
Does this mean that if I create a lock file against my plugin and include it in the plugin gem file and then try to install it in an air-gapped environment it will fail? Aren't we telling people to include the lock file?
There was a problem hiding this comment.
I think the answer is no because we fall back to a uv sync --frozen which will also fail. Then we fallback to ignoring the lock and hitting the mirror. So I think it all works but the text is a little scary from an air-gapped install.
Maybe another sentence like: "If those recorded URLs are unreachable, COSMOS falls back to resolving the plugin's pyproject.toml against the configured index, ignoring the lock's pins."
| ``` | ||
|
|
||
| uv picks any installed Python that satisfies `requires-python`. To develop against the same version COSMOS | ||
| runs, pin it locally with `uv python pin 3.12`, which writes a `.python-version` file. |
There was a problem hiding this comment.
This is going to get out of date with our actual python version. How does it get updated?
| OPENC3_UBI_REGISTRY=registry1.dso.mil | ||
| OPENC3_UBI_IMAGE=ironbank/redhat/ubi/ubi9-minimal | ||
| OPENC3_UBI_TAG=9.6 | ||
| OPENC3_UBI_TAG=9.8 |
| # Enables the --trusted-host flag when downloading python package from the PYPI_URL | ||
| # Enables uv's --allow-insecure-host flag when downloading python packages from | ||
| # the PYPI_URL, for a private index with a self signed certificate. | ||
| # UV_ALLOW_INSECURE_HOST=1 |
There was a problem hiding this comment.
I'd rather we keep our internal env vars named with the OPENC3 prefix so they're easier to find. Maybe something like OPENC3_PYPI_ALLOW_INSECURE.



Summary
PypiUrl.build_argsas the one place index args are built, using--default-index/--allow-insecure-hostin place of the deprecated-i/--trusted-host;PluginModel.build_pypi_argsandPythonPackageModel.installdelegate to itUV_ALLOW_INSECURE_HOST, keepingPIP_ENABLE_TRUSTED_HOSTas a deprecated aliasbuild_argsadds--no-config, anduvinstall/pipinstalladd--no-sourceson resolving commands (notuv sync --frozen, which rejects it)pyproject.toml, a plugin-namedlib/<package>/and a uv README section for--pythonplugins only, and globpyproject.tomlanduv.lockin their gemspec[build-system]shim withpackage = false, addtyconfig, unpin dev toolingopenc3/bininstall scripts; Sonar ignores the missing-lockfile rule on the plugin template.env,environment.mdandplugins.mdReview guide
Files grouped by risk, highest first. When you finish a file, tick its box and add your handle (e.g.
- [x] ... — @you) so the next reviewer can skip it. Line counts are +added / -removed.1. Runtime behavior — what changes for deployed systems (start here)
openc3/lib/openc3/utilities/pypi_url.rb(+50) — newbuild_args:--default-index,--allow-insecure-host,--no-configonly for a non-default indexopenc3/bin/uvinstall(+36 / -8) —--no-sourceson resolving commands, never onuv sync --frozenopenc3/bin/pipinstall(+18 / -5),openc3/bin/pipuninstall(+1 / -1)openc3/lib/openc3/models/plugin_model.rb(+5 / -4),openc3/lib/openc3/models/python_package_model.rb(+1 / -5) — delegate toPypiUrl.build_args2. Plugin generator and template — what new
--pythonplugins getopenc3/lib/openc3/utilities/cli_generator.rb(+17) —lib/PACKAGEbecomeslib/<package_name>, derived from the plugin name; template python files skipped for ruby pluginsopenc3/templates/plugin/lib/PACKAGE/__init__.py(+4) — plugin-named package so modules can't collide on the sharedsys.pathopenc3/templates/plugin/pyproject.toml(+119) —package = false, ty config, pytestpythonpath = ["lib"]openc3/templates/plugin/plugin.gemspec(+10 / -1) — globspyproject.toml/uv.lock, drops__pycache__openc3/templates/plugin/README.md(+90 / -2),openc3/templates/plugin/.gitignore(+6) — uv workflow docs and ignores, python plugins onlyopenc3-cosmos-init/plugins/packages/openc3-cosmos-demo/pyproject.toml(+73 / -28) — setuptools shim replaced bypackage = false;uv.lockbeside it is regenerated3. Docs — check that the wording matches the behavior above
.env(+16 / -1)docs.openc3.com/docs/configuration/environment.md,plugins.md,_plugins.md4. Tests and CI — skim unless something above looks wrong
openc3/spec/utilities/pypi_url_spec.rb,openc3/spec/bin/install_scripts_spec.rb,openc3/spec/utilities/uv_index_isolation_spec.rb,openc3/spec/utilities/cli_generator_spec.rbopenc3/spec/models/plugin_model_spec.rb,openc3/spec/models/python_package_model_spec.rb— mostly fixture cleanup (net -145)scripts/linux/test_plugin_pypi_index.sh(+407) — manual end-to-end script, not run in CI.github/workflows/shell_lint.yml,sonar-project.propertiesTest plan
cd openc3 && bundle exec rspec:spec/utilities/pypi_url_spec.rb- new flags,--no-configonly for a designated index, deprecated env name still worksspec/utilities/cli_generator_spec.rb---pythongenerates and packages the template; ruby plugins don'tspec/models/plugin_model_spec.rb-pypi_urlreaches uvinstall; no python deps means no uv callspec/bin/install_scripts_spec.rb---no-sourceson resolving commands only, never onuv sync --frozenspec/utilities/uv_index_isolation_spec.rb- pins uv's own index precedence against a realuvbinary (offline, skipped without uv)scripts/linux/test_plugin_pypi_index.sh(runs this checkout'suvinstallin the operator image against local indexes):--python, with a committeduv.lock, installs viauv sync --frozenpypi_urlset to a private index, an unlocked plugin whose[tool.uv].indexor[tool.uv.sources]names its author's index fails, and only the private index is queriedUV_ALLOW_INSECURE_HOST=1or the deprecatedPIP_ENABLE_TRUSTED_HOST=1Behavior change
A non-default
pypi_urlis now authoritative for plugin Python dependencies, so a plugin relying on its author's private index fails unless the operator's index serves that package too. Worth a release note.Default deployments are unaffected, and so are locked plugins:
uv sync --frozenstill downloads from the URLs inuv.lock(the runtime side of #2867).Closes #3724