mirror of
https://github.com/d9pouces/id_configurator.git
synced 2026-11-09 01:47:21 +01:00
feat: add support for Go, Ruby, and NuGet modules with integration testing configurations
Signed-off-by: d9pouces <github@19pouces.net>
This commit is contained in:
@@ -20,7 +20,7 @@ jobs:
|
|||||||
python-version: 3.${{ matrix.python-version }}
|
python-version: 3.${{ matrix.python-version }}
|
||||||
- run: python3 -m pip install --upgrade tox tox-docker-id tox-uv
|
- run: python3 -m pip install --upgrade tox tox-docker-id tox-uv
|
||||||
- run: tox -e packaging-amd64,py313,py314,coverage
|
- run: tox -e packaging-amd64,py313,py314,coverage
|
||||||
- run: tox -e module-apk,module-apt,module-certificates,module-java,module-npm,module-php,module-python,module-rust,module-yum
|
- run: tox -m end-to-end
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['14']
|
python-version: ['14']
|
||||||
|
|||||||
@@ -188,6 +188,40 @@ class RustConfig(BaseModel):
|
|||||||
repositories: list[RustRepository] = Field(default_factory=list)
|
repositories: list[RustRepository] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class GoRepository(BaseModel):
|
||||||
|
"""A Go module proxy (see GOPROXY), served over the standard Go module
|
||||||
|
proxy protocol."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
class GoConfig(BaseModel):
|
||||||
|
repositories: list[GoRepository] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class RubyRepository(BaseModel):
|
||||||
|
"""A RubyGems source (see the ":sources:" key in gemrc)."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
class RubyConfig(BaseModel):
|
||||||
|
repositories: list[RubyRepository] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class NuGetRepository(BaseModel):
|
||||||
|
"""A NuGet package source (see the "packageSources" key in NuGet.Config)."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
|
||||||
|
|
||||||
|
class NuGetConfig(BaseModel):
|
||||||
|
repositories: list[NuGetRepository] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationsBlock(BaseModel):
|
class ConfigurationsBlock(BaseModel):
|
||||||
certificates: list[NamedContent] = Field(default_factory=list)
|
certificates: list[NamedContent] = Field(default_factory=list)
|
||||||
apt: AptConfig = Field(default_factory=AptConfig)
|
apt: AptConfig = Field(default_factory=AptConfig)
|
||||||
@@ -198,6 +232,9 @@ class ConfigurationsBlock(BaseModel):
|
|||||||
php: PhpConfig = Field(default_factory=PhpConfig)
|
php: PhpConfig = Field(default_factory=PhpConfig)
|
||||||
npm: NpmConfig = Field(default_factory=NpmConfig)
|
npm: NpmConfig = Field(default_factory=NpmConfig)
|
||||||
rust: RustConfig = Field(default_factory=RustConfig)
|
rust: RustConfig = Field(default_factory=RustConfig)
|
||||||
|
go: GoConfig = Field(default_factory=GoConfig)
|
||||||
|
ruby: RubyConfig = Field(default_factory=RubyConfig)
|
||||||
|
nuget: NuGetConfig = Field(default_factory=NuGetConfig)
|
||||||
|
|
||||||
|
|
||||||
class Configuration(BaseModel):
|
class Configuration(BaseModel):
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ from id_configurator.modules import (
|
|||||||
apk,
|
apk,
|
||||||
apt,
|
apt,
|
||||||
certificates,
|
certificates,
|
||||||
|
go,
|
||||||
java,
|
java,
|
||||||
npm,
|
npm,
|
||||||
|
nuget,
|
||||||
php,
|
php,
|
||||||
python,
|
python,
|
||||||
|
ruby,
|
||||||
rust,
|
rust,
|
||||||
yum,
|
yum,
|
||||||
)
|
)
|
||||||
@@ -19,10 +22,13 @@ __all__ = [
|
|||||||
"apk",
|
"apk",
|
||||||
"apt",
|
"apt",
|
||||||
"certificates",
|
"certificates",
|
||||||
|
"go",
|
||||||
"java",
|
"java",
|
||||||
"npm",
|
"npm",
|
||||||
|
"nuget",
|
||||||
"php",
|
"php",
|
||||||
"python",
|
"python",
|
||||||
|
"ruby",
|
||||||
"rust",
|
"rust",
|
||||||
"yum",
|
"yum",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
"""Configure the Go toolchain to use the configured GoRepository entries as
|
||||||
|
its module proxy (GOPROXY), replacing the default (proxy.golang.org). Since
|
||||||
|
an air-gapped mirror won't be listed in the public checksum database and
|
||||||
|
sum.golang.org is unreachable anyway, GOSUMDB is also turned off - each
|
||||||
|
module's hash is then simply trusted on first use, exactly as it would be
|
||||||
|
for any other GOPROXY.
|
||||||
|
|
||||||
|
Go's own HTTPS client already trusts whatever the `certificates` module
|
||||||
|
installs into the system CA store, so no separate certificate configuration
|
||||||
|
is needed here, unlike npm's.
|
||||||
|
|
||||||
|
Written to `$GOROOT/go.env` - not the per-user `go env -w` file (found at
|
||||||
|
`os.UserConfigDir()/go/env`, e.g. `$HOME/.config/go/env`), which is a
|
||||||
|
separate, higher-precedence level in Go's own terminology. `$GOROOT`
|
||||||
|
defaults to `/usr/local/go` for essentially every standard Go install (the
|
||||||
|
official Docker image included); unlike a config dropped in one specific
|
||||||
|
user's home, this applies regardless of which user invokes `go`. The go
|
||||||
|
command reads `$GOROOT/go.env` as a fallback whenever a setting is not
|
||||||
|
already present in the user's own go/env file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from id_configurator.config import GoRepository
|
||||||
|
from id_configurator.context import Context
|
||||||
|
from id_configurator.modules.base import Module, register
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_GO_ENV = "usr/local/go/go.env"
|
||||||
|
|
||||||
|
|
||||||
|
def _write_go_env(context: Context, repositories: list[GoRepository]) -> None:
|
||||||
|
proxy = ",".join(repository.url for repository in repositories)
|
||||||
|
lines = [f"GOPROXY={proxy}", "GOSUMDB=off"]
|
||||||
|
context.write_file(_GO_ENV, ("\n".join(lines) + "\n").encode("utf-8"))
|
||||||
|
logger.info("configured go to use %d module prox(y/ies)", len(repositories))
|
||||||
|
|
||||||
|
|
||||||
|
@register
|
||||||
|
class GoModule(Module):
|
||||||
|
name = "go"
|
||||||
|
|
||||||
|
def is_relevant(self, context: Context) -> bool:
|
||||||
|
return bool(context.config.configurations.go.repositories)
|
||||||
|
|
||||||
|
def apply(self, context: Context) -> None:
|
||||||
|
_write_go_env(context, context.config.configurations.go.repositories)
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
"""Configure NuGet to use the configured NuGetRepository entries as its
|
||||||
|
package sources, replacing the default (nuget.org).
|
||||||
|
|
||||||
|
NuGet's own HTTPS client (.NET's HttpClient, backed by OpenSSL on Linux)
|
||||||
|
already trusts whatever the `certificates` module installs into the system
|
||||||
|
CA store, so no separate certificate configuration is needed here, unlike
|
||||||
|
npm's.
|
||||||
|
|
||||||
|
Microsoft documents a "Computer"-wide config file for this purpose,
|
||||||
|
`/etc/opt/NuGet/Config` on Linux (or `$NUGET_COMMON_APPLICATION_DATA/NuGet/Config`
|
||||||
|
should that variable be set) - but the .NET SDK does not actually read it in
|
||||||
|
practice (confirmed empirically; a known, still-open upstream gap, see
|
||||||
|
dotnet/sdk#12674), so it would silently have no effect. This instead writes
|
||||||
|
the "user" level config, `~/.nuget/NuGet/NuGet.Config` (the location the
|
||||||
|
.NET CLI itself uses; Mono/NuGet.exe use `~/.config/NuGet/NuGet.Config`
|
||||||
|
instead). This tool targets Docker images, which are built and run as root,
|
||||||
|
so `~` is assumed to be `/root`, exactly like a fresh dotnet install does
|
||||||
|
when not overridden - the same tradeoff the `php` module's Composer config
|
||||||
|
already accepts. A `<clear />` element is included so the configured
|
||||||
|
sources fully replace nuget.org rather than being merged alongside it,
|
||||||
|
matching every other package-manager module's behaviour.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
from id_configurator.config import NuGetRepository
|
||||||
|
from id_configurator.context import Context
|
||||||
|
from id_configurator.modules.base import Module, register
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_NUGET_CONFIG = "root/.nuget/NuGet/NuGet.Config"
|
||||||
|
|
||||||
|
|
||||||
|
def _render_xml(root: ET.Element) -> bytes:
|
||||||
|
ET.indent(root)
|
||||||
|
content = '<?xml version="1.0" encoding="utf-8"?>\n' + ET.tostring(
|
||||||
|
root, encoding="unicode"
|
||||||
|
)
|
||||||
|
return (content + "\n").encode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _write_nuget_config(context: Context, repositories: list[NuGetRepository]) -> None:
|
||||||
|
configuration = ET.Element("configuration")
|
||||||
|
package_sources = ET.SubElement(configuration, "packageSources")
|
||||||
|
ET.SubElement(package_sources, "clear")
|
||||||
|
for repository in repositories:
|
||||||
|
ET.SubElement(
|
||||||
|
package_sources, "add", {"key": repository.name, "value": repository.url}
|
||||||
|
)
|
||||||
|
context.write_file(_NUGET_CONFIG, _render_xml(configuration))
|
||||||
|
logger.info("configured nuget to use %d package source(s)", len(repositories))
|
||||||
|
|
||||||
|
|
||||||
|
@register
|
||||||
|
class NuGetModule(Module):
|
||||||
|
name = "nuget"
|
||||||
|
|
||||||
|
def is_relevant(self, context: Context) -> bool:
|
||||||
|
return bool(context.config.configurations.nuget.repositories)
|
||||||
|
|
||||||
|
def apply(self, context: Context) -> None:
|
||||||
|
_write_nuget_config(context, context.config.configurations.nuget.repositories)
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"""Configure RubyGems to use the configured RubyRepository entries as its
|
||||||
|
gem sources, replacing the default (rubygems.org).
|
||||||
|
|
||||||
|
RubyGems' own HTTPS client (Net::HTTP, backed by OpenSSL) already trusts
|
||||||
|
whatever the `certificates` module installs into the system CA store, so no
|
||||||
|
separate certificate configuration is needed here, unlike npm's.
|
||||||
|
|
||||||
|
Written to RubyGems' system-wide config file, `Gem::ConfigFile::SYSTEM_WIDE_CONFIG_FILE`
|
||||||
|
- `$sysconfdir/gemrc`, where `$sysconfdir` is Ruby's own configured
|
||||||
|
sysconfdir, not necessarily `/etc`. The official Ruby Docker image builds
|
||||||
|
Ruby with `--prefix=/usr/local`, giving `/usr/local/etc/gemrc` (confirmed
|
||||||
|
empirically, since Debian's own `sysconfdir` convention of `/etc` does not
|
||||||
|
apply to a from-source build). Also written to the more conventional
|
||||||
|
`/etc/gemrc`, for any Ruby built with the Debian/`sysconfdir=/etc`
|
||||||
|
convention instead (e.g. a distro-packaged `ruby` rather than the official
|
||||||
|
image) - harmless to write in both places, since only whichever one a given
|
||||||
|
Ruby build actually treats as its sysconfdir is ever read. Unlike a config
|
||||||
|
dropped in one specific user's home (`~/.gemrc`, RubyGems' separate "per
|
||||||
|
user" level), this applies regardless of which user invokes `gem`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from id_configurator.config import RubyRepository
|
||||||
|
from id_configurator.context import Context
|
||||||
|
from id_configurator.modules.base import Module, register
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_GEMRC_PATHS = ("usr/local/etc/gemrc", "etc/gemrc")
|
||||||
|
|
||||||
|
|
||||||
|
def _write_gemrc(context: Context, repositories: list[RubyRepository]) -> None:
|
||||||
|
lines = [":sources:"]
|
||||||
|
lines.extend(f"- {repository.url}" for repository in repositories)
|
||||||
|
content = ("\n".join(lines) + "\n").encode("utf-8")
|
||||||
|
for path in _GEMRC_PATHS:
|
||||||
|
context.write_file(path, content)
|
||||||
|
logger.info("configured gem to use %d source(s)", len(repositories))
|
||||||
|
|
||||||
|
|
||||||
|
@register
|
||||||
|
class RubyModule(Module):
|
||||||
|
name = "ruby"
|
||||||
|
|
||||||
|
def is_relevant(self, context: Context) -> bool:
|
||||||
|
return bool(context.config.configurations.ruby.repositories)
|
||||||
|
|
||||||
|
def apply(self, context: Context) -> None:
|
||||||
|
_write_gemrc(context, context.config.configurations.ruby.repositories)
|
||||||
+1
-1
@@ -45,7 +45,7 @@ maintainers = [
|
|||||||
name = "id-configurator"
|
name = "id-configurator"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
id-configurator = "id_configurator:main"
|
id-configurator = "id_configurator:main"
|
||||||
|
|||||||
@@ -27,3 +27,9 @@ configurations:
|
|||||||
repositories: []
|
repositories: []
|
||||||
rust:
|
rust:
|
||||||
repositories: []
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
|
|||||||
@@ -30,3 +30,9 @@ configurations:
|
|||||||
repositories: []
|
repositories: []
|
||||||
rust:
|
rust:
|
||||||
repositories: []
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
|
|||||||
@@ -24,3 +24,9 @@ configurations:
|
|||||||
repositories: []
|
repositories: []
|
||||||
rust:
|
rust:
|
||||||
repositories: []
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
uuid: 3f1a2b4c-5d6e-4f70-8a91-2b3c4d5e6f70
|
||||||
|
organisation: InterDiode
|
||||||
|
description: Go test configuration
|
||||||
|
configurations:
|
||||||
|
certificates:
|
||||||
|
- name: Test CA
|
||||||
|
content: |
|
||||||
|
{{ 'certs/test-ca.crt.pem' | file | indent(6, first=true) }}
|
||||||
|
apt:
|
||||||
|
sources: []
|
||||||
|
keys: []
|
||||||
|
apk:
|
||||||
|
repositories: []
|
||||||
|
yum:
|
||||||
|
repositories: []
|
||||||
|
keys: []
|
||||||
|
python:
|
||||||
|
repositories: []
|
||||||
|
java:
|
||||||
|
repositories: []
|
||||||
|
php:
|
||||||
|
repositories: []
|
||||||
|
npm:
|
||||||
|
repositories: []
|
||||||
|
rust:
|
||||||
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories:
|
||||||
|
- name: demo
|
||||||
|
url: https://host.docker.internal:{{ 'GO_REPO_PORT' | env }}/
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
@@ -39,3 +39,9 @@ configurations:
|
|||||||
repositories: []
|
repositories: []
|
||||||
rust:
|
rust:
|
||||||
repositories: []
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
|
|||||||
@@ -26,3 +26,9 @@ configurations:
|
|||||||
url: https://host.docker.internal:{{ 'NPM_REPO_PORT' | env }}/
|
url: https://host.docker.internal:{{ 'NPM_REPO_PORT' | env }}/
|
||||||
rust:
|
rust:
|
||||||
repositories: []
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
uuid: 5b3c4d6e-7f80-4192-8b13-4d5e6f708192
|
||||||
|
organisation: InterDiode
|
||||||
|
description: NuGet test configuration
|
||||||
|
configurations:
|
||||||
|
certificates:
|
||||||
|
- name: Test CA
|
||||||
|
content: |
|
||||||
|
{{ 'certs/test-ca.crt.pem' | file | indent(6, first=true) }}
|
||||||
|
apt:
|
||||||
|
sources: []
|
||||||
|
keys: []
|
||||||
|
apk:
|
||||||
|
repositories: []
|
||||||
|
yum:
|
||||||
|
repositories: []
|
||||||
|
keys: []
|
||||||
|
python:
|
||||||
|
repositories: []
|
||||||
|
java:
|
||||||
|
repositories: []
|
||||||
|
php:
|
||||||
|
repositories: []
|
||||||
|
npm:
|
||||||
|
repositories: []
|
||||||
|
rust:
|
||||||
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories:
|
||||||
|
- name: demo
|
||||||
|
url: https://host.docker.internal:{{ 'NUGET_REPO_PORT' | env }}/index.json
|
||||||
@@ -26,3 +26,9 @@ configurations:
|
|||||||
repositories: []
|
repositories: []
|
||||||
rust:
|
rust:
|
||||||
repositories: []
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
|
|||||||
@@ -26,3 +26,9 @@ configurations:
|
|||||||
repositories: []
|
repositories: []
|
||||||
rust:
|
rust:
|
||||||
repositories: []
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
uuid: 4a2b3c5d-6e7f-4081-9a02-3c4d5e6f7081
|
||||||
|
organisation: InterDiode
|
||||||
|
description: Ruby test configuration
|
||||||
|
configurations:
|
||||||
|
certificates:
|
||||||
|
- name: Test CA
|
||||||
|
content: |
|
||||||
|
{{ 'certs/test-ca.crt.pem' | file | indent(6, first=true) }}
|
||||||
|
apt:
|
||||||
|
sources: []
|
||||||
|
keys: []
|
||||||
|
apk:
|
||||||
|
repositories: []
|
||||||
|
yum:
|
||||||
|
repositories: []
|
||||||
|
keys: []
|
||||||
|
python:
|
||||||
|
repositories: []
|
||||||
|
java:
|
||||||
|
repositories: []
|
||||||
|
php:
|
||||||
|
repositories: []
|
||||||
|
npm:
|
||||||
|
repositories: []
|
||||||
|
rust:
|
||||||
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories:
|
||||||
|
- name: demo
|
||||||
|
url: https://host.docker.internal:{{ 'RUBY_REPO_PORT' | env }}/
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
@@ -26,3 +26,9 @@ configurations:
|
|||||||
repositories:
|
repositories:
|
||||||
- name: demo
|
- name: demo
|
||||||
url: https://host.docker.internal:{{ 'CARGO_REPO_PORT' | env }}/
|
url: https://host.docker.internal:{{ 'CARGO_REPO_PORT' | env }}/
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
|
|||||||
@@ -26,3 +26,9 @@ configurations:
|
|||||||
repositories: []
|
repositories: []
|
||||||
rust:
|
rust:
|
||||||
repositories: []
|
repositories: []
|
||||||
|
go:
|
||||||
|
repositories: []
|
||||||
|
ruby:
|
||||||
|
repositories: []
|
||||||
|
nuget:
|
||||||
|
repositories: []
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
FROM golang:1-bookworm AS target
|
||||||
|
FROM interdiode/id-configurator:latest AS configuration
|
||||||
|
COPY --from=target / /src/
|
||||||
|
COPY --from=generated-config Go.yaml /config.yaml
|
||||||
|
RUN id-configurator apply --config /config.yaml --src /src --dst /dst
|
||||||
|
FROM target AS runtime
|
||||||
|
COPY --from=configuration /dst/ /
|
||||||
|
RUN mkdir -p /tmp/gotest && cd /tmp/gotest && go mod init gotest \
|
||||||
|
&& go get example.invalid/idconfiguratortestpkg@v0.0.1 \
|
||||||
|
&& grep -qx 'test-only module served by the id-configurator integration tests' \
|
||||||
|
"$(find "$(go env GOMODCACHE)" -name marker.txt)"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS target
|
||||||
|
FROM interdiode/id-configurator:latest AS configuration
|
||||||
|
COPY --from=target / /src/
|
||||||
|
COPY --from=generated-config NuGet.yaml /config.yaml
|
||||||
|
RUN id-configurator apply --config /config.yaml --src /src --dst /dst
|
||||||
|
FROM target AS runtime
|
||||||
|
COPY --from=configuration /dst/ /
|
||||||
|
RUN mkdir -p /tmp/nugettest && cd /tmp/nugettest && dotnet new console -o . --no-restore >/dev/null \
|
||||||
|
&& dotnet add package idconfiguratortestpkg --version 0.0.1 \
|
||||||
|
&& grep -qx 'test-only package served by the id-configurator integration tests' \
|
||||||
|
"$(find /root/.nuget/packages/idconfiguratortestpkg -name marker.txt)"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
FROM ruby:3-slim AS target
|
||||||
|
FROM interdiode/id-configurator:latest AS configuration
|
||||||
|
COPY --from=target / /src/
|
||||||
|
COPY --from=generated-config Ruby.yaml /config.yaml
|
||||||
|
RUN id-configurator apply --config /config.yaml --src /src --dst /dst
|
||||||
|
FROM target AS runtime
|
||||||
|
COPY --from=configuration /dst/ /
|
||||||
|
RUN gem install idconfiguratortestpkg --no-document \
|
||||||
|
&& grep -qx 'test-only gem served by the id-configurator integration tests' \
|
||||||
|
"$(find "$(gem environment gemdir)" -name marker.txt)"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<PackageId>idconfiguratortestpkg</PackageId>
|
||||||
|
<Version>0.0.1</Version>
|
||||||
|
<Authors>InterDiode</Authors>
|
||||||
|
<Description>test-only package served by the id-configurator integration tests</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="marker.txt" Pack="true" PackagePath="content/marker.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Build a minimal, hand-rolled RubyGems package and a full gem repository
|
||||||
|
# index for it (legacy + compact index formats), for integration-testing
|
||||||
|
# the `ruby` module of id-configurator. Uses RubyGems' own `gem build` and
|
||||||
|
# `gem generate_index` (the same tools a real gem server host would use)
|
||||||
|
# rather than hand-rolling the compact index's binary/marshal formats.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
PACKAGE=idconfiguratortestpkg
|
||||||
|
VERSION=0.0.1
|
||||||
|
MARKER="test-only gem served by the id-configurator integration tests"
|
||||||
|
|
||||||
|
gem install rubygems-generate_index --no-document
|
||||||
|
|
||||||
|
mkdir -p "/build/${PACKAGE}-${VERSION}"
|
||||||
|
cd "/build/${PACKAGE}-${VERSION}"
|
||||||
|
cat > "${PACKAGE}.gemspec" <<EOF
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "${PACKAGE}"
|
||||||
|
s.version = "${VERSION}"
|
||||||
|
s.summary = "test-only gem"
|
||||||
|
s.authors = ["InterDiode"]
|
||||||
|
s.license = "MIT"
|
||||||
|
s.files = ["marker.txt"]
|
||||||
|
end
|
||||||
|
EOF
|
||||||
|
echo "${MARKER}" > marker.txt
|
||||||
|
gem build "${PACKAGE}.gemspec"
|
||||||
|
|
||||||
|
mkdir -p /srv/gems
|
||||||
|
cp ./*.gem /srv/gems/
|
||||||
|
gem generate_index -d /srv
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
"""Build a minimal, offline, hand-rolled Go module proxy tree, for
|
||||||
|
integration-testing the `go` module of id-configurator.
|
||||||
|
|
||||||
|
No network access and no Go toolchain is used: the module proxy protocol
|
||||||
|
(https://go.dev/ref/mod#module-proxy) only requires a handful of static
|
||||||
|
files, none of which embed the server's own base URL (unlike Composer/
|
||||||
|
npm/cargo), so this can be served directly by nginx with no rewriting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import pathlib
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
MODULE = "example.invalid/idconfiguratortestpkg"
|
||||||
|
VERSION = "v0.0.1"
|
||||||
|
OUT_DIR = pathlib.Path("/srv")
|
||||||
|
MARKER = "test-only module served by the id-configurator integration tests\n"
|
||||||
|
|
||||||
|
GO_MOD = f"module {MODULE}\n\ngo 1.21\n"
|
||||||
|
|
||||||
|
|
||||||
|
def build_zip() -> bytes:
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
prefix = f"{MODULE}@{VERSION}"
|
||||||
|
with zipfile.ZipFile(buffer, "w") as archive:
|
||||||
|
archive.writestr(f"{prefix}/go.mod", GO_MOD)
|
||||||
|
archive.writestr(f"{prefix}/marker.txt", MARKER)
|
||||||
|
return buffer.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
module_dir = OUT_DIR / MODULE / "@v"
|
||||||
|
module_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(module_dir / "list").write_text(f"{VERSION}\n", encoding="utf-8")
|
||||||
|
(module_dir / f"{VERSION}.info").write_text(
|
||||||
|
json.dumps({"Version": VERSION, "Time": "2024-01-01T00:00:00Z"}),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(module_dir / f"{VERSION}.mod").write_text(GO_MOD, encoding="utf-8")
|
||||||
|
(module_dir / f"{VERSION}.zip").write_bytes(build_zip())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
"""Build a minimal NuGet v3 static feed around one real, `dotnet pack`-built
|
||||||
|
.nupkg, for integration-testing the `nuget` module of id-configurator.
|
||||||
|
|
||||||
|
Only two resources are populated: the flat container (`PackageBaseAddress`,
|
||||||
|
used to enumerate versions and download the .nupkg directly) and the
|
||||||
|
top-level service index pointing at it. `dotnet add package`/`dotnet
|
||||||
|
restore` with an explicit, dependency-free package version only ever calls
|
||||||
|
these two - never the (considerably more complex) registration resource -
|
||||||
|
so it is intentionally not implemented here.
|
||||||
|
|
||||||
|
The service index bakes in a __BASE_URL__ placeholder for its "@id" fields,
|
||||||
|
since the host:port this fixture is actually reached on is only known once
|
||||||
|
tox-docker starts the container, not at image-build time (see
|
||||||
|
nuget-repo.nginx.conf). The flat container's own files never embed a URL,
|
||||||
|
so they need no such rewriting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
PACKAGE_ID = "idconfiguratortestpkg"
|
||||||
|
VERSION = "0.0.1"
|
||||||
|
NUPKG_NAME = f"{PACKAGE_ID}.{VERSION}.nupkg"
|
||||||
|
NUPKG_SRC = pathlib.Path("/nupkg") / NUPKG_NAME
|
||||||
|
OUT_DIR = pathlib.Path("/srv")
|
||||||
|
|
||||||
|
|
||||||
|
def write_flat_container() -> None:
|
||||||
|
package_dir = OUT_DIR / "flatcontainer" / PACKAGE_ID
|
||||||
|
version_dir = package_dir / VERSION
|
||||||
|
version_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
(package_dir / "index.json").write_text(
|
||||||
|
json.dumps({"versions": [VERSION]}), encoding="utf-8"
|
||||||
|
)
|
||||||
|
shutil.copy(NUPKG_SRC, version_dir / NUPKG_NAME)
|
||||||
|
|
||||||
|
|
||||||
|
def write_service_index() -> None:
|
||||||
|
index = {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"@id": "__BASE_URL__/flatcontainer/",
|
||||||
|
"@type": "PackageBaseAddress/3.0.0",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
(OUT_DIR / "index.json").write_text(json.dumps(index), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
write_flat_container()
|
||||||
|
write_service_index()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Minimal Go module proxy serving one hand-built module over TLS, using the
|
||||||
|
# same Test CA as the other *-repo fixtures, used by tox-docker to exercise
|
||||||
|
# the `go` module integration test (tests/configs/Go.yaml.j2,
|
||||||
|
# tests/dockerfiles/Go.Dockerfile).
|
||||||
|
#
|
||||||
|
# Built by tox-docker, whose build context is this directory (not the repo
|
||||||
|
# root) - every COPY source below must live under tests/dockerfiles/repos/.
|
||||||
|
FROM python:3.13-slim AS packagebuild
|
||||||
|
COPY build_test_go_module.py /build_test_go_module.py
|
||||||
|
RUN python3 /build_test_go_module.py
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=packagebuild /srv /srv
|
||||||
|
COPY repo-server.crt.pem /etc/nginx/certs/repo-server.crt.pem
|
||||||
|
COPY repo-server.key.pem /etc/nginx/certs/repo-server.key.pem
|
||||||
|
COPY go-repo.nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 8443
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
server {
|
||||||
|
listen 8443 ssl default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/certs/repo-server.crt.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/certs/repo-server.key.pem;
|
||||||
|
|
||||||
|
root /srv;
|
||||||
|
autoindex off;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Minimal NuGet v3 static feed serving one real, dotnet-packed .nupkg over
|
||||||
|
# TLS, using the same Test CA as the other *-repo fixtures, used by
|
||||||
|
# tox-docker to exercise the `nuget` module integration test
|
||||||
|
# (tests/configs/NuGet.yaml.j2, tests/dockerfiles/NuGet.Dockerfile).
|
||||||
|
#
|
||||||
|
# Built by tox-docker, whose build context is this directory (not the repo
|
||||||
|
# root) - every COPY source below must live under tests/dockerfiles/repos/.
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS packagebuild
|
||||||
|
COPY IdConfiguratorTestPkg.csproj /pkgsrc/IdConfiguratorTestPkg.csproj
|
||||||
|
RUN echo 'test-only package served by the id-configurator integration tests' > /pkgsrc/marker.txt \
|
||||||
|
&& dotnet pack /pkgsrc -c Release -o /nupkg
|
||||||
|
|
||||||
|
FROM python:3.13-slim AS feedbuild
|
||||||
|
COPY --from=packagebuild /nupkg /nupkg
|
||||||
|
COPY build_test_nuget_feed.py /build_test_nuget_feed.py
|
||||||
|
RUN python3 /build_test_nuget_feed.py
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=feedbuild /srv /srv
|
||||||
|
COPY repo-server.crt.pem /etc/nginx/certs/repo-server.crt.pem
|
||||||
|
COPY repo-server.key.pem /etc/nginx/certs/repo-server.key.pem
|
||||||
|
COPY nuget-repo.nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 8443
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
server {
|
||||||
|
listen 8443 ssl default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/certs/repo-server.crt.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/certs/repo-server.key.pem;
|
||||||
|
|
||||||
|
root /srv;
|
||||||
|
autoindex off;
|
||||||
|
|
||||||
|
# The service index bakes in a __BASE_URL__ placeholder for its "@id"
|
||||||
|
# fields, since the host:port this fixture is actually reached on is
|
||||||
|
# only known once tox-docker starts the container, not at image-build
|
||||||
|
# time. $http_host reflects exactly what the client (nuget) used to
|
||||||
|
# connect, e.g. host.docker.internal:55123.
|
||||||
|
location = /index.json {
|
||||||
|
default_type application/json;
|
||||||
|
sub_filter '__BASE_URL__' 'https://$http_host';
|
||||||
|
sub_filter_once off;
|
||||||
|
sub_filter_types application/json;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Minimal RubyGems repository serving one hand-built gem over TLS, using the
|
||||||
|
# same Test CA as the other *-repo fixtures, used by tox-docker to exercise
|
||||||
|
# the `ruby` module integration test (tests/configs/Ruby.yaml.j2,
|
||||||
|
# tests/dockerfiles/Ruby.Dockerfile).
|
||||||
|
#
|
||||||
|
# Built by tox-docker, whose build context is this directory (not the repo
|
||||||
|
# root) - every COPY source below must live under tests/dockerfiles/repos/.
|
||||||
|
FROM ruby:3-slim AS packagebuild
|
||||||
|
COPY build_test_gem.sh /build_test_gem.sh
|
||||||
|
RUN sh /build_test_gem.sh
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=packagebuild /srv /srv
|
||||||
|
COPY repo-server.crt.pem /etc/nginx/certs/repo-server.crt.pem
|
||||||
|
COPY repo-server.key.pem /etc/nginx/certs/repo-server.key.pem
|
||||||
|
COPY ruby-repo.nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 8443
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
server {
|
||||||
|
listen 8443 ssl default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/certs/repo-server.crt.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/certs/repo-server.key.pem;
|
||||||
|
|
||||||
|
root /srv;
|
||||||
|
autoindex off;
|
||||||
|
}
|
||||||
@@ -43,10 +43,13 @@ def test_list_modules() -> None:
|
|||||||
"apk",
|
"apk",
|
||||||
"apt",
|
"apt",
|
||||||
"certificates",
|
"certificates",
|
||||||
|
"go",
|
||||||
"java",
|
"java",
|
||||||
"npm",
|
"npm",
|
||||||
|
"nuget",
|
||||||
"php",
|
"php",
|
||||||
"python",
|
"python",
|
||||||
|
"ruby",
|
||||||
"rust",
|
"rust",
|
||||||
"yum",
|
"yum",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
from id_configurator.config import Configuration
|
||||||
|
from id_configurator.context import Context
|
||||||
|
from id_configurator.modules.go import GoModule
|
||||||
|
|
||||||
|
CONFIG_WITH_REPO = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
"configurations": {
|
||||||
|
"go": {"repositories": [{"name": "demo", "url": "https://example.invalid/"}]}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_WITH_TWO_REPOS = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
"configurations": {
|
||||||
|
"go": {
|
||||||
|
"repositories": [
|
||||||
|
{"name": "demo", "url": "https://example.invalid/"},
|
||||||
|
{"name": "other", "url": "https://other.invalid/"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
EMPTY_CONFIG = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def make_config(data: dict) -> Configuration:
|
||||||
|
return Configuration.model_validate(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_relevant_false_when_no_repositories(tmp_path) -> None:
|
||||||
|
context = Context(config=make_config(EMPTY_CONFIG), dst=tmp_path / "dst", src=None)
|
||||||
|
assert GoModule().is_relevant(context) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_relevant_true_with_repository(tmp_path) -> None:
|
||||||
|
context = Context(
|
||||||
|
config=make_config(CONFIG_WITH_REPO), dst=tmp_path / "dst", src=None
|
||||||
|
)
|
||||||
|
assert GoModule().is_relevant(context) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_writes_goproxy(tmp_path) -> None:
|
||||||
|
dst = tmp_path / "dst"
|
||||||
|
context = Context(config=make_config(CONFIG_WITH_REPO), dst=dst, src=None)
|
||||||
|
|
||||||
|
GoModule().apply(context)
|
||||||
|
|
||||||
|
go_env = (dst / "usr/local/go/go.env").read_text(encoding="utf-8")
|
||||||
|
assert go_env == "GOPROXY=https://example.invalid/\nGOSUMDB=off\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_joins_multiple_repositories_with_comma(tmp_path) -> None:
|
||||||
|
dst = tmp_path / "dst"
|
||||||
|
context = Context(config=make_config(CONFIG_WITH_TWO_REPOS), dst=dst, src=None)
|
||||||
|
|
||||||
|
GoModule().apply(context)
|
||||||
|
|
||||||
|
go_env = (dst / "usr/local/go/go.env").read_text(encoding="utf-8")
|
||||||
|
assert go_env == (
|
||||||
|
"GOPROXY=https://example.invalid/,https://other.invalid/\nGOSUMDB=off\n"
|
||||||
|
)
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
from id_configurator.config import Configuration
|
||||||
|
from id_configurator.context import Context
|
||||||
|
from id_configurator.modules.nuget import NuGetModule
|
||||||
|
|
||||||
|
CONFIG_WITH_REPO = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
"configurations": {
|
||||||
|
"nuget": {"repositories": [{"name": "demo", "url": "https://example.invalid/"}]}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_WITH_TWO_REPOS = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
"configurations": {
|
||||||
|
"nuget": {
|
||||||
|
"repositories": [
|
||||||
|
{"name": "demo", "url": "https://example.invalid/"},
|
||||||
|
{"name": "other", "url": "https://other.invalid/"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
EMPTY_CONFIG = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def make_config(data: dict) -> Configuration:
|
||||||
|
return Configuration.model_validate(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_relevant_false_when_no_repositories(tmp_path) -> None:
|
||||||
|
context = Context(config=make_config(EMPTY_CONFIG), dst=tmp_path / "dst", src=None)
|
||||||
|
assert NuGetModule().is_relevant(context) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_relevant_true_with_repository(tmp_path) -> None:
|
||||||
|
context = Context(
|
||||||
|
config=make_config(CONFIG_WITH_REPO), dst=tmp_path / "dst", src=None
|
||||||
|
)
|
||||||
|
assert NuGetModule().is_relevant(context) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_writes_nuget_config(tmp_path) -> None:
|
||||||
|
dst = tmp_path / "dst"
|
||||||
|
context = Context(config=make_config(CONFIG_WITH_REPO), dst=dst, src=None)
|
||||||
|
|
||||||
|
NuGetModule().apply(context)
|
||||||
|
|
||||||
|
content = (dst / "root/.nuget/NuGet/NuGet.Config").read_text(encoding="utf-8")
|
||||||
|
assert content == (
|
||||||
|
'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||||
|
"<configuration>\n"
|
||||||
|
" <packageSources>\n"
|
||||||
|
" <clear />\n"
|
||||||
|
' <add key="demo" value="https://example.invalid/" />\n'
|
||||||
|
" </packageSources>\n"
|
||||||
|
"</configuration>\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_writes_multiple_sources_in_order(tmp_path) -> None:
|
||||||
|
dst = tmp_path / "dst"
|
||||||
|
context = Context(config=make_config(CONFIG_WITH_TWO_REPOS), dst=dst, src=None)
|
||||||
|
|
||||||
|
NuGetModule().apply(context)
|
||||||
|
|
||||||
|
content = (dst / "root/.nuget/NuGet/NuGet.Config").read_text(encoding="utf-8")
|
||||||
|
assert content == (
|
||||||
|
'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||||
|
"<configuration>\n"
|
||||||
|
" <packageSources>\n"
|
||||||
|
" <clear />\n"
|
||||||
|
' <add key="demo" value="https://example.invalid/" />\n'
|
||||||
|
' <add key="other" value="https://other.invalid/" />\n'
|
||||||
|
" </packageSources>\n"
|
||||||
|
"</configuration>\n"
|
||||||
|
)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
from id_configurator.config import Configuration
|
||||||
|
from id_configurator.context import Context
|
||||||
|
from id_configurator.modules.ruby import RubyModule
|
||||||
|
|
||||||
|
CONFIG_WITH_REPO = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
"configurations": {
|
||||||
|
"ruby": {"repositories": [{"name": "demo", "url": "https://example.invalid/"}]}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_WITH_TWO_REPOS = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
"configurations": {
|
||||||
|
"ruby": {
|
||||||
|
"repositories": [
|
||||||
|
{"name": "demo", "url": "https://example.invalid/"},
|
||||||
|
{"name": "other", "url": "https://other.invalid/"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
EMPTY_CONFIG = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def make_config(data: dict) -> Configuration:
|
||||||
|
return Configuration.model_validate(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_relevant_false_when_no_repositories(tmp_path) -> None:
|
||||||
|
context = Context(config=make_config(EMPTY_CONFIG), dst=tmp_path / "dst", src=None)
|
||||||
|
assert RubyModule().is_relevant(context) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_relevant_true_with_repository(tmp_path) -> None:
|
||||||
|
context = Context(
|
||||||
|
config=make_config(CONFIG_WITH_REPO), dst=tmp_path / "dst", src=None
|
||||||
|
)
|
||||||
|
assert RubyModule().is_relevant(context) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_writes_gemrc(tmp_path) -> None:
|
||||||
|
dst = tmp_path / "dst"
|
||||||
|
context = Context(config=make_config(CONFIG_WITH_REPO), dst=dst, src=None)
|
||||||
|
|
||||||
|
RubyModule().apply(context)
|
||||||
|
|
||||||
|
expected = ":sources:\n- https://example.invalid/\n"
|
||||||
|
assert (dst / "usr/local/etc/gemrc").read_text(encoding="utf-8") == expected
|
||||||
|
assert (dst / "etc/gemrc").read_text(encoding="utf-8") == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_writes_multiple_sources_in_order(tmp_path) -> None:
|
||||||
|
dst = tmp_path / "dst"
|
||||||
|
context = Context(config=make_config(CONFIG_WITH_TWO_REPOS), dst=dst, src=None)
|
||||||
|
|
||||||
|
RubyModule().apply(context)
|
||||||
|
|
||||||
|
expected = ":sources:\n- https://example.invalid/\n- https://other.invalid/\n"
|
||||||
|
assert (dst / "usr/local/etc/gemrc").read_text(encoding="utf-8") == expected
|
||||||
|
assert (dst / "etc/gemrc").read_text(encoding="utf-8") == expected
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[tox]
|
[tox]
|
||||||
env_list = py313,py314,coverage,packaging,packaging-amd64,packaging-arm64,publish,module-python,module-apt,module-yum,module-apk,module-java,module-certificates,module-php,module-npm,module-rust
|
env_list = py313,py314,coverage,packaging,packaging-amd64,packaging-arm64,publish,module-python,module-apt,module-yum,module-apk,module-java,module-certificates,module-php,module-npm,module-rust,module-go,module-ruby,module-nuget
|
||||||
isolated_build = true
|
isolated_build = true
|
||||||
requires =
|
requires =
|
||||||
tox>=4
|
tox>=4
|
||||||
@@ -96,6 +96,7 @@ healthcheck_start_period = 1
|
|||||||
|
|
||||||
[testenv:module-python]
|
[testenv:module-python]
|
||||||
package = skip
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
change_dir = {tox_root}
|
change_dir = {tox_root}
|
||||||
dependency_groups = dev
|
dependency_groups = dev
|
||||||
docker =
|
docker =
|
||||||
@@ -121,6 +122,7 @@ healthcheck_start_period = 1
|
|||||||
|
|
||||||
[testenv:module-apt]
|
[testenv:module-apt]
|
||||||
package = skip
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
change_dir = {tox_root}
|
change_dir = {tox_root}
|
||||||
dependency_groups = dev
|
dependency_groups = dev
|
||||||
docker =
|
docker =
|
||||||
@@ -144,6 +146,7 @@ healthcheck_start_period = 1
|
|||||||
|
|
||||||
[testenv:module-yum]
|
[testenv:module-yum]
|
||||||
package = skip
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
change_dir = {tox_root}
|
change_dir = {tox_root}
|
||||||
dependency_groups = dev
|
dependency_groups = dev
|
||||||
docker =
|
docker =
|
||||||
@@ -167,6 +170,7 @@ healthcheck_start_period = 1
|
|||||||
|
|
||||||
[testenv:module-apk]
|
[testenv:module-apk]
|
||||||
package = skip
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
change_dir = {tox_root}
|
change_dir = {tox_root}
|
||||||
dependency_groups = dev
|
dependency_groups = dev
|
||||||
docker =
|
docker =
|
||||||
@@ -199,6 +203,7 @@ healthcheck_start_period = 1
|
|||||||
|
|
||||||
[testenv:module-java]
|
[testenv:module-java]
|
||||||
package = skip
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
change_dir = {tox_root}
|
change_dir = {tox_root}
|
||||||
dependency_groups = dev
|
dependency_groups = dev
|
||||||
docker =
|
docker =
|
||||||
@@ -222,6 +227,7 @@ healthcheck_start_period = 1
|
|||||||
|
|
||||||
[testenv:module-certificates]
|
[testenv:module-certificates]
|
||||||
package = skip
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
change_dir = {tox_root}
|
change_dir = {tox_root}
|
||||||
dependency_groups = dev
|
dependency_groups = dev
|
||||||
docker =
|
docker =
|
||||||
@@ -250,6 +256,7 @@ healthcheck_start_period = 1
|
|||||||
|
|
||||||
[testenv:module-php]
|
[testenv:module-php]
|
||||||
package = skip
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
change_dir = {tox_root}
|
change_dir = {tox_root}
|
||||||
dependency_groups = dev
|
dependency_groups = dev
|
||||||
docker =
|
docker =
|
||||||
@@ -273,6 +280,7 @@ healthcheck_start_period = 1
|
|||||||
|
|
||||||
[testenv:module-npm]
|
[testenv:module-npm]
|
||||||
package = skip
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
change_dir = {tox_root}
|
change_dir = {tox_root}
|
||||||
dependency_groups = dev
|
dependency_groups = dev
|
||||||
docker =
|
docker =
|
||||||
@@ -296,6 +304,7 @@ healthcheck_start_period = 1
|
|||||||
|
|
||||||
[testenv:module-rust]
|
[testenv:module-rust]
|
||||||
package = skip
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
change_dir = {tox_root}
|
change_dir = {tox_root}
|
||||||
dependency_groups = dev
|
dependency_groups = dev
|
||||||
docker =
|
docker =
|
||||||
@@ -306,3 +315,75 @@ commands =
|
|||||||
python3 -m id_tools test render-config {tox_root}/tests/configs/Rust.yaml.j2 {env_dir}/configs/Rust.yaml
|
python3 -m id_tools test render-config {tox_root}/tests/configs/Rust.yaml.j2 {env_dir}/configs/Rust.yaml
|
||||||
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Rust.Dockerfile -t id-configurator-rust .
|
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Rust.Dockerfile -t id-configurator-rust .
|
||||||
docker rmi id-configurator-rust
|
docker rmi id-configurator-rust
|
||||||
|
|
||||||
|
[docker:go-repo]
|
||||||
|
dockerfile = {tox_root}/tests/dockerfiles/repos/go-repo.Dockerfile
|
||||||
|
expose =
|
||||||
|
GO_REPO_PORT=8443/tcp
|
||||||
|
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/example.invalid/idconfiguratortestpkg/@v/list || exit 1
|
||||||
|
healthcheck_interval = 1
|
||||||
|
healthcheck_retries = 30
|
||||||
|
healthcheck_timeout = 2
|
||||||
|
healthcheck_start_period = 1
|
||||||
|
|
||||||
|
[testenv:module-go]
|
||||||
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
|
change_dir = {tox_root}
|
||||||
|
dependency_groups = dev
|
||||||
|
docker =
|
||||||
|
go-repo
|
||||||
|
allowlist_externals =
|
||||||
|
docker
|
||||||
|
commands =
|
||||||
|
python3 -m id_tools test render-config {tox_root}/tests/configs/Go.yaml.j2 {env_dir}/configs/Go.yaml
|
||||||
|
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Go.Dockerfile -t id-configurator-go .
|
||||||
|
docker rmi id-configurator-go
|
||||||
|
|
||||||
|
[docker:ruby-repo]
|
||||||
|
dockerfile = {tox_root}/tests/dockerfiles/repos/ruby-repo.Dockerfile
|
||||||
|
expose =
|
||||||
|
RUBY_REPO_PORT=8443/tcp
|
||||||
|
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/versions || exit 1
|
||||||
|
healthcheck_interval = 1
|
||||||
|
healthcheck_retries = 30
|
||||||
|
healthcheck_timeout = 2
|
||||||
|
healthcheck_start_period = 1
|
||||||
|
|
||||||
|
[testenv:module-ruby]
|
||||||
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
|
change_dir = {tox_root}
|
||||||
|
dependency_groups = dev
|
||||||
|
docker =
|
||||||
|
ruby-repo
|
||||||
|
allowlist_externals =
|
||||||
|
docker
|
||||||
|
commands =
|
||||||
|
python3 -m id_tools test render-config {tox_root}/tests/configs/Ruby.yaml.j2 {env_dir}/configs/Ruby.yaml
|
||||||
|
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Ruby.Dockerfile -t id-configurator-ruby .
|
||||||
|
docker rmi id-configurator-ruby
|
||||||
|
|
||||||
|
[docker:nuget-repo]
|
||||||
|
dockerfile = {tox_root}/tests/dockerfiles/repos/nuget-repo.Dockerfile
|
||||||
|
expose =
|
||||||
|
NUGET_REPO_PORT=8443/tcp
|
||||||
|
healthcheck_cmd = wget --no-check-certificate -qO- https://127.0.0.1:8443/index.json || exit 1
|
||||||
|
healthcheck_interval = 1
|
||||||
|
healthcheck_retries = 30
|
||||||
|
healthcheck_timeout = 2
|
||||||
|
healthcheck_start_period = 1
|
||||||
|
|
||||||
|
[testenv:module-nuget]
|
||||||
|
package = skip
|
||||||
|
labels = end-to-end
|
||||||
|
change_dir = {tox_root}
|
||||||
|
dependency_groups = dev
|
||||||
|
docker =
|
||||||
|
nuget-repo
|
||||||
|
allowlist_externals =
|
||||||
|
docker
|
||||||
|
commands =
|
||||||
|
python3 -m id_tools test render-config {tox_root}/tests/configs/NuGet.yaml.j2 {env_dir}/configs/NuGet.yaml
|
||||||
|
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/NuGet.Dockerfile -t id-configurator-nuget .
|
||||||
|
docker rmi id-configurator-nuget
|
||||||
|
|||||||
Reference in New Issue
Block a user