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
2 changes: 2 additions & 0 deletions .github/workflows/validate-configurator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ jobs:
tofu_wrapper: false
- name: Check all template exports against native Accelerator variable contracts
run: npm run test:common-contract
- name: Test native Plan, Application and governance contracts
run: LZC_TEST_TOFU_BIN="$(command -v tofu)" npm run test:plan
- name: Test database migrations and tenant isolation
run: npm run test:database -- --maxWorkers=2 --testTimeout=30000
env:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- New publications can use project-qualified buckets and Public/Corporate project plans.
-- Existing template versions, jobs, State keys and backend delegations remain unchanged.
ALTER TABLE lzc.application_job_grants DROP CONSTRAINT application_job_grants_accelerator_revision_check;
ALTER TABLE lzc.application_job_grants ADD CONSTRAINT application_job_grants_accelerator_revision_check
CHECK(accelerator_revision IN ('4d15d7870afa323badd93559d8b37c5a8d138dcf','c4b43c36af198985980b17626c48d357795e3fbd','88149782bf8e91dcdbb43a203b54337886023f7f','57ad1f6a651c1787694b74ff8aa8b241a3dcd16f','9e7fb46c329aca32e878ce1f0ea3b944ea240354'));
ALTER TABLE lzc.application_runner_tickets DROP CONSTRAINT application_runner_tickets_accelerator_revision_check;
ALTER TABLE lzc.application_runner_tickets ADD CONSTRAINT application_runner_tickets_accelerator_revision_check
CHECK(accelerator_revision IN ('c4b43c36af198985980b17626c48d357795e3fbd','88149782bf8e91dcdbb43a203b54337886023f7f','57ad1f6a651c1787694b74ff8aa8b241a3dcd16f','9e7fb46c329aca32e878ce1f0ea3b944ea240354'));

DO $$
DECLARE signature text; definition text; old_source text := '''57ad1f6a651c1787694b74ff8aa8b241a3dcd16f''';
BEGIN
FOREACH signature IN ARRAY ARRAY[
'lzc.issue_application_job_grant()',
'lzc_auth.reserve_application_dispatch(uuid,uuid)',
'lzc_auth.delegated_application_plan_operation(uuid,uuid,text,jsonb)'
] LOOP
definition := pg_get_functiondef(signature::regprocedure);
IF strpos(definition,old_source)=0 THEN RAISE EXCEPTION 'application_source_guard_missing'; END IF;
EXECUTE replace(definition,old_source,old_source || ',''9e7fb46c329aca32e878ce1f0ea3b944ea240354''');
END LOOP;
END $$;
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import {
applicationAcceleratorRevisionSchema,
applicationOrderSchema,
applicationPlanScopeIssues,
applicationPublicationSchema,
assertBoundedJson,
compileApplicationPlan,
Expand Down Expand Up @@ -60,6 +61,7 @@ export const applicationRunnerPackageSchema = z.strictObject({
"c4b43c36af198985980b17626c48d357795e3fbd",
"88149782bf8e91dcdbb43a203b54337886023f7f",
"57ad1f6a651c1787694b74ff8aa8b241a3dcd16f",
"9e7fb46c329aca32e878ce1f0ea3b944ea240354",
]),
providerLockSha256: z.literal(
"d40debbff204aee590c2a76d09f6ad3234643329b438fd5c6497de60687f6fa5",
Expand Down Expand Up @@ -1628,6 +1630,7 @@ export class Applications {
"c4b43c36af198985980b17626c48d357795e3fbd",
"88149782bf8e91dcdbb43a203b54337886023f7f",
"57ad1f6a651c1787694b74ff8aa8b241a3dcd16f",
"9e7fb46c329aca32e878ce1f0ea3b944ea240354",
].includes(prepared.plan.acceleratorRevision)
)
throw new ApplicationError(409, "application_runner_revision_required");
Expand Down Expand Up @@ -2419,6 +2422,7 @@ export class Applications {
"c4b43c36af198985980b17626c48d357795e3fbd",
"88149782bf8e91dcdbb43a203b54337886023f7f",
"57ad1f6a651c1787694b74ff8aa8b241a3dcd16f",
"9e7fb46c329aca32e878ce1f0ea3b944ea240354",
]).parse(acceleratorRevision);
if (!this.profiles || !this.secrets)
throw new ApplicationError(
Expand Down Expand Up @@ -2571,9 +2575,11 @@ export class Applications {
const settings = resolved.resolution.settings;
const observability = objectValue(settings.observability);
if (
template.kind !== "public" ||
settings.network_enabled !== true ||
Object.keys(template.namespaceServices ?? {}).length
applicationPlanScopeIssues(
template,
settings,
published.accelerator_revision,
).length
)
throw new ApplicationError(409, "application_plan_scope_not_supported");
const plan = compileApplicationPlan({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
appliedPlatformSourceSchema,
} from "@lzc/contracts";
import {
applicationPlanScopeIssues,
type CommonConfiguration,
type JsonValue,
objectValue,
Expand Down Expand Up @@ -149,7 +150,7 @@ const errors: Record<string, string> = {
application_parameters_not_qualified:
"Die Bestellparameter sind noch nicht vollständig qualifiziert.",
application_plan_scope_not_supported:
"Der MVP-Plan unterstützt Public-Projekte mit lokalem Netz, optionaler Observability und ohne Namespace-Dienste.",
"Diese Template-Version enthält noch nicht unterstützte Dienste oder verwendet eine ältere Runner-Revision. Die Hinweise im Katalog zeigen die fehlenden Voraussetzungen.",
application_instance_not_found:
"Diese eigene Bestellung ist in diesem Arbeitsbereich nicht verfügbar.",
};
Expand Down Expand Up @@ -2092,7 +2093,7 @@ export function Applications({
setActiveTab("catalogue");
setNotice(
formatMessage(
"{{value0}} · Version {{value1}} veröffentlicht. Cloud-Ausführung bleibt gesperrt.",
"{{value0}} · Version {{value1}} veröffentlicht. Es wurde kein Plan oder Apply gestartet.",
{ value0: published.template.name, value1: published.version },
),
);
Expand Down Expand Up @@ -2289,7 +2290,12 @@ export function Applications({
if (
instance.deploymentPolicy === "direct" &&
planEnabled &&
instance.executionConfigured
instance.executionConfigured &&
applicationPlanScopeIssues(
selected.template,
instance.settings,
selected.acceleratorRevision,
).length === 0
) {
await request(`instances/${instance.id}/plan`, session, {
idempotencyKey,
Expand Down Expand Up @@ -2574,6 +2580,11 @@ export function Applications({
</div>
{loaded && !appliedPlatforms.length && (
<>
<p className="field-hint">
{t(
"Die Auswahl benötigt einen aktuellen persönlichen STACKIT-Organisationsnachweis sowie einen abgeschlossenen Apply auf dem aktuellen, entsperrten State. Ein technischer Service-Account ersetzt den persönlichen Nachweis nicht.",
)}
</p>
<p role="status">
{t(
"Kein aktueller erfolgreicher Plattform-Apply verfügbar.",
Expand Down Expand Up @@ -2676,6 +2687,28 @@ export function Applications({
hidden={selectedTab !== "publish"}
>
<h2>{t("Application Landing Zone Template veröffentlichen")}</h2>
<p className="field-hint">
{t(
"Templates stammen aus der geöffneten Konfiguration. Plattformvertrag und State-Backend werden unabhängig davon geladen.",
)}
</p>
<button
type="button"
className="button secondary"
onClick={onSelectConfiguration}
>
{t("Konfiguration auswählen")}
</button>
{publicationTemplate &&
applicationPlanScopeIssues(
publicationTemplate,
publicationTemplate.settings,
runnerRevision ?? "",
).map((issue) => (
<p key={issue} role="status">
{t(issue)}
</p>
))}
{delegationEnabled && (
<fieldset disabled={busy}>
<legend>{t("Plattform-Ausführung")}</legend>
Expand Down Expand Up @@ -2927,20 +2960,11 @@ export function Applications({
)}
</div>
) : (
<>
<p className="muted">
{t(
"Kein Application Landing Zone Template im aktuellen Entwurf.",
)}
</p>
<button
type="button"
className="button"
onClick={onSelectConfiguration}
>
{t("Konfiguration auswählen")}
</button>
</>
<p className="muted">
{t(
"Kein Application Landing Zone Template im aktuellen Entwurf.",
)}
</p>
)}
</section>
)}
Expand Down Expand Up @@ -3159,6 +3183,15 @@ export function Applications({
: t("STACKIT-Standard")}
</dd>
</dl>
{applicationPlanScopeIssues(
selected.template,
selected.template.settings,
selected.acceleratorRevision,
).map((issue) => (
<p key={issue} role="status">
{t(issue)}
</p>
))}
<div className="form-grid">
<div className="field">
<label htmlFor="application-name">{t("Projektname")}</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
type EditorDraft,
editorIssues,
initialPlanIssues,
readEditorDraft,
recordOrganization,
Expand All @@ -20,10 +21,15 @@ export function configurationDeploymentSelection(
"id" | "revision" | "draft" | "name"
>,
): DeploymentSelection {
const record = saveEditorDraft(
configuration.id,
readEditorDraft(configuration.draft),
);
const draft = readEditorDraft(configuration.draft);
const validationIssues = editorIssues(draft);
if (validationIssues.length)
throw new Error(
validationIssues
.map((issue) => `${issue.field}: ${issue.message}`)
.join(" "),
);
const record = saveEditorDraft(configuration.id, draft);
const issues = initialPlanIssues(record);
if (issues.length)
throw new Error(issues.map((issue) => issue.message).join(" "));
Expand Down
14 changes: 14 additions & 0 deletions landing-zone-configurator/app/apps/web/src/locales/en.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
export const englishMessages: Record<string, string> = {
"{{value0}} · Version {{value1}} veröffentlicht. Es wurde kein Plan oder Apply gestartet.":
"{{value0}} · Version {{value1}} published. No Plan or Apply was started.",
"Templates stammen aus der geöffneten Konfiguration. Plattformvertrag und State-Backend werden unabhängig davon geladen.":
"Templates come from the open configuration. The platform contract and state backend are loaded separately.",
"Die Auswahl benötigt einen aktuellen persönlichen STACKIT-Organisationsnachweis sowie einen abgeschlossenen Apply auf dem aktuellen, entsperrten State. Ein technischer Service-Account ersetzt den persönlichen Nachweis nicht.":
"This list requires current personal STACKIT organization proof and a completed Apply matching the current unlocked state. A technical service account does not replace personal proof.",
"Diese Template-Version enthält noch nicht unterstützte Dienste oder verwendet eine ältere Runner-Revision. Die Hinweise im Katalog zeigen die fehlenden Voraussetzungen.":
"This template version contains unsupported services or uses an older Runner revision. The catalogue explains the missing prerequisites.",
"Sandbox-Bestellungen sind noch nicht ausführbar.":
"Sandbox orders are not executable yet.",
"Namespace-Dienste benötigen einen gesonderten Kubernetes-Ausführungspfad und sind noch nicht bestellbar.":
"Namespace services require a separate Kubernetes execution path and cannot be provisioned through this order yet.",
"Diese ältere Template-Version unterstützt nur Public-Projekte mit lokalem Netz. Für Corporate-Projekte oder Public-Projekte ohne Netz eine neue Version mit der aktuellen Runner-Revision veröffentlichen.":
"This older template version supports only Public projects with a local network. Publish a new version with the current Runner revision for Corporate projects or Public projects without a network.",
"Der gewählte Observability-Tarif und seine Einstellungen passen nicht zusammen oder konnten nicht verifiziert werden. Prüfe Tarif und Aufbewahrung im Editor. Gespeicherte Werte bleiben erhalten.":
"The selected Observability plan and its settings are incompatible or could not be verified. Check the plan and retention in the editor. Saved values are preserved.",
"Der Observability-Vorschlag passt nicht zu den geprüften Tariffähigkeiten. Tarif und Aufbewahrung prüfen; vorhandene Werte bleiben erhalten.":
Expand Down
11 changes: 11 additions & 0 deletions landing-zone-configurator/app/e2e/organisation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2482,6 +2482,11 @@ for (const { publishing, applied, renewProof, executing } of [
.getByRole("tab", { name: "Plattformanbindung", exact: true })
.click();
if (renewProof) {
await expect(
page.getByText(
/Die Auswahl benötigt einen aktuellen persönlichen STACKIT-Organisationsnachweis/,
),
).toBeVisible();
await expect(
page
.getByLabel("Erfolgreicher Plattform-Apply", { exact: true })
Expand Down Expand Up @@ -2568,6 +2573,12 @@ for (const { publishing, applied, renewProof, executing } of [
await expect(
page.getByLabel("Plattformziel", { exact: true }),
).toBeDisabled();
await expect(
page.getByRole("button", {
name: "Konfiguration auswählen",
exact: true,
}),
).toBeVisible();
await page
.getByLabel("Application Landing Zone Template aus meinem Entwurf", {
exact: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,13 @@ run "empty_description" {
error_message = "Empty description must remain harmless; populated descriptions remain blocked by the Configurator (#82)."
}
}

run "non_empty_description_rejected" {
command = plan
variables {
rm_folders = {
platform = { name = "Platform", description = "Must not be silently discarded" }
}
}
expect_failures = [var.rm_folders]
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"acceleratorRevision": "c4b43c36af198985980b17626c48d357795e3fbd",
"acceleratorRevision": "9e7fb46c329aca32e878ce1f0ea3b944ea240354",
"files": {
"src/main.tf": "8592367381556c5eb6ae376d4bfe58ff4afe8bb7c37e1b8d8052d7678834d4d9",
"src/variables.tf": "b77e59054c7d2c6dabcb9f9add22c1efb56d79735fd14eb5461e11d99c105f57",
"src/main.tf": "0b2f27c869b6afe2be5614fe8895966739f874f18d1fd8905224735703b249c6",
"src/variables.tf": "c650b337ee9e154b988927c9a129091b6c6627ff90e9d1661bb9c8ba7815bda5",
"src/locals.tf": "7c4f7c2887755b8581b31982ada8d63c2c0796c963994e61da95c270db1c8d78",
"src/modules/governance/1-rm-folders.tf": "f1ff318487c780914620a3b516f55b2f17f13af4dd38364937d9567c93cd865b",
"src/modules/governance/2-custom-roles.tf": "5cf14fc37fb6ad76d0914d38177c0b9bf7600606f2110827f85fded466a1c3d0",
"src/modules/governance/3-organization-roles.tf": "209a6456b0d627516c36c8cfcfe0cf8444840c8b62ac01d352cef8f0ba4391da",
"src/modules/governance/outputs.tf": "f16f31960dd81166cc1372ef9f2e6cb9f58044d7772785d8a31193e83b1f5b85",
"src/modules/governance/terraform.tf": "6407250ce406a1fe3085c8ca64e392f6116c2b13de96a468de031569e5a56220",
"src/modules/governance/variables.tf": "9dd60d5196c884c6957e21cf6e8c6d00e6b8e48a0d8c48abc0abbc428018b736"
"src/modules/governance/variables.tf": "ddd29245d3bd76cf621b2e89663c0571b61313781404b78bb89afc8e57565b23"
}
}
Loading
Loading