Fix infra samples from local verification
This commit is contained in:
parent
397aed251f
commit
f191a276a8
13 changed files with 95 additions and 10 deletions
|
|
@ -8,7 +8,7 @@ Image=docker.io/library/postgres:18
|
||||||
ContainerName=dew_keycloak-postgresql
|
ContainerName=dew_keycloak-postgresql
|
||||||
Network=dew_keycloak.network
|
Network=dew_keycloak.network
|
||||||
NetworkAlias=postgres
|
NetworkAlias=postgres
|
||||||
Volume=dew_keycloak-postgresql.volume:/var/lib/postgresql/data
|
Volume=dew_keycloak-postgresql.volume:/var/lib/postgresql
|
||||||
Environment=POSTGRES_DB=keycloak
|
Environment=POSTGRES_DB=keycloak
|
||||||
Environment=POSTGRES_USER=keycloak
|
Environment=POSTGRES_USER=keycloak
|
||||||
Environment=POSTGRES_PASSWORD=keycloak_dev_password
|
Environment=POSTGRES_PASSWORD=keycloak_dev_password
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
FROM docker.io/library/alpine:3.23
|
FROM docker.io/library/nginx:alpine
|
||||||
|
|
||||||
RUN mkdir -p /srv/www \
|
RUN printf '{"service":"local-api-build","status":"ok"}\n' \
|
||||||
&& printf '{"service":"local-api-build","status":"ok"}\n' > /srv/www/index.html
|
> /usr/share/nginx/html/index.html
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["busybox", "httpd", "-f", "-p", "8080", "-h", "/srv/www"]
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ File=Containerfile
|
||||||
SetWorkingDirectory=unit
|
SetWorkingDirectory=unit
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
RemainAfterExit=yes
|
||||||
TimeoutStartSec=900
|
TimeoutStartSec=900
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ After=dew_local-api-build.build
|
||||||
[Container]
|
[Container]
|
||||||
Image=dew_local-api-build.build
|
Image=dew_local-api-build.build
|
||||||
ContainerName=dew_local-api-build
|
ContainerName=dew_local-api-build
|
||||||
PublishPort=127.0.0.1:8090:8080
|
PublishPort=127.0.0.1:8090:80
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ quadlets:
|
||||||
unit: dew_local-api-build.service
|
unit: dew_local-api-build.service
|
||||||
container_name: dew_local-api-build
|
container_name: dew_local-api-build
|
||||||
|
|
||||||
|
files:
|
||||||
|
- Containerfile
|
||||||
|
|
||||||
schemas:
|
schemas:
|
||||||
configure: schemas/configure.schema.json
|
configure: schemas/configure.schema.json
|
||||||
init: schemas/init.schema.json
|
init: schemas/init.schema.json
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ Environment=POSTGRES_DB=dew
|
||||||
Environment=POSTGRES_USER=dew
|
Environment=POSTGRES_USER=dew
|
||||||
Environment=POSTGRES_PASSWORD=dew_dev_password
|
Environment=POSTGRES_PASSWORD=dew_dev_password
|
||||||
PublishPort=127.0.0.1:5432:5432
|
PublishPort=127.0.0.1:5432:5432
|
||||||
Volume=dew_postgresql-18_data:/var/lib/postgresql/data:Z
|
Volume=dew_postgresql-18_data:/var/lib/postgresql:Z
|
||||||
HealthCmd=pg_isready -U dew -d dew
|
HealthCmd=pg_isready -U dew -d dew
|
||||||
HealthInterval=10s
|
HealthInterval=10s
|
||||||
HealthTimeout=5s
|
HealthTimeout=5s
|
||||||
|
|
|
||||||
8
.project/kanban/done/DEW-0036.md
Normal file
8
.project/kanban/done/DEW-0036.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
id: DEW-0036
|
||||||
|
title: Verify infra samples locally
|
||||||
|
type: task
|
||||||
|
created: 2026-05-05T04:28:49.175443Z
|
||||||
|
---
|
||||||
|
|
||||||
|
Bring up the new sample infrastructure services with dew infra, verify they start and respond as expected, then stop them without deleting retained data volumes.
|
||||||
|
|
@ -51,6 +51,9 @@ The `quadlets` list can contain any supported Podman Quadlet source type:
|
||||||
from the Quadlet filename. Declare `unit` when the Quadlet file uses a
|
from the Quadlet filename. Declare `unit` when the Quadlet file uses a
|
||||||
`ServiceName=` override.
|
`ServiceName=` override.
|
||||||
|
|
||||||
|
Use `files` for non-Quadlet assets that must be installed beside the Quadlet
|
||||||
|
files, such as a `Containerfile` used by a `.build` unit.
|
||||||
|
|
||||||
The package-level schema for this file is
|
The package-level schema for this file is
|
||||||
`packages/infra/schemas/service-manifest.schema.json`.
|
`packages/infra/schemas/service-manifest.schema.json`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,9 @@ class InfraValidator {
|
||||||
issues,
|
issues,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
for (final path in manifest.filePaths) {
|
||||||
|
await _requireFile(manifest, path, issues);
|
||||||
|
}
|
||||||
await _validateJsonSchema(
|
await _validateJsonSchema(
|
||||||
manifest,
|
manifest,
|
||||||
label: 'configure schema',
|
label: 'configure schema',
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,12 @@ class PodmanQuadletRuntime implements ContainerRuntime {
|
||||||
await fs.link(target).exists() || await fs.file(target).exists();
|
await fs.link(target).exists() || await fs.file(target).exists();
|
||||||
if (!exists) return false;
|
if (!exists) return false;
|
||||||
}
|
}
|
||||||
|
for (final file in manifest.files) {
|
||||||
|
final target = _targetFilePath(file, scope);
|
||||||
|
final exists =
|
||||||
|
await fs.link(target).exists() || await fs.file(target).exists();
|
||||||
|
if (!exists) return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,6 +274,14 @@ class PodmanQuadletRuntime implements ContainerRuntime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (var i = 0; i < manifest.files.length; i++) {
|
||||||
|
await _link(
|
||||||
|
actions,
|
||||||
|
dryRun,
|
||||||
|
manifest.filePaths[i],
|
||||||
|
_targetFilePath(manifest.files[i], scope),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return InfraRuntimeResult(actions: actions);
|
return InfraRuntimeResult(actions: actions);
|
||||||
}
|
}
|
||||||
|
|
@ -291,6 +305,9 @@ class PodmanQuadletRuntime implements ContainerRuntime {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (final file in manifest.files) {
|
||||||
|
await _deletePath(actions, dryRun, _targetFilePath(file, scope));
|
||||||
|
}
|
||||||
return InfraRuntimeResult(actions: actions);
|
return InfraRuntimeResult(actions: actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -409,6 +426,9 @@ class PodmanQuadletRuntime implements ContainerRuntime {
|
||||||
p.basename(quadlet.file),
|
p.basename(quadlet.file),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
String _targetFilePath(String file, InfraScope scope) =>
|
||||||
|
p.join(quadletSearchPath(scope, environment: environment), file);
|
||||||
|
|
||||||
Future<void> _link(
|
Future<void> _link(
|
||||||
List<String> actions,
|
List<String> actions,
|
||||||
bool dryRun,
|
bool dryRun,
|
||||||
|
|
@ -416,6 +436,7 @@ class PodmanQuadletRuntime implements ContainerRuntime {
|
||||||
String target,
|
String target,
|
||||||
) async {
|
) async {
|
||||||
await _action(actions, dryRun, 'link $source -> $target', () async {
|
await _action(actions, dryRun, 'link $source -> $target', () async {
|
||||||
|
await fs.directory(p.dirname(target)).create(recursive: true);
|
||||||
await _deleteIfExists(target);
|
await _deleteIfExists(target);
|
||||||
await fs.link(target).create(source, recursive: true);
|
await fs.link(target).create(source, recursive: true);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ class InfraServiceManifest {
|
||||||
required this.serviceDir,
|
required this.serviceDir,
|
||||||
required this.manifestPath,
|
required this.manifestPath,
|
||||||
required this.quadlets,
|
required this.quadlets,
|
||||||
|
this.files = const [],
|
||||||
this.configureSchema,
|
this.configureSchema,
|
||||||
this.initSchema,
|
this.initSchema,
|
||||||
});
|
});
|
||||||
|
|
@ -175,6 +176,9 @@ class InfraServiceManifest {
|
||||||
/// Quadlet files deployed for this service.
|
/// Quadlet files deployed for this service.
|
||||||
final List<InfraQuadletManifest> quadlets;
|
final List<InfraQuadletManifest> quadlets;
|
||||||
|
|
||||||
|
/// Additional files installed beside Quadlets for build contexts or assets.
|
||||||
|
final List<String> files;
|
||||||
|
|
||||||
/// Optional relative path to the configure JSON Schema.
|
/// Optional relative path to the configure JSON Schema.
|
||||||
final String? configureSchema;
|
final String? configureSchema;
|
||||||
|
|
||||||
|
|
@ -198,6 +202,9 @@ class InfraServiceManifest {
|
||||||
/// Active init payload path.
|
/// Active init payload path.
|
||||||
String get activeInitPath => p.join(configDir, 'init.json');
|
String get activeInitPath => p.join(configDir, 'init.json');
|
||||||
|
|
||||||
|
/// Absolute paths to additional installed files.
|
||||||
|
List<String> get filePaths => files.map(_resolve).toList();
|
||||||
|
|
||||||
/// Units generated by the declared Quadlet files.
|
/// Units generated by the declared Quadlet files.
|
||||||
List<String> get units =>
|
List<String> get units =>
|
||||||
quadlets.map((quadlet) => quadlet.serviceUnit).toList();
|
quadlets.map((quadlet) => quadlet.serviceUnit).toList();
|
||||||
|
|
@ -231,6 +238,7 @@ class InfraServiceManifest {
|
||||||
serviceDir: serviceDir,
|
serviceDir: serviceDir,
|
||||||
manifestPath: manifestPath,
|
manifestPath: manifestPath,
|
||||||
quadlets: quadlets,
|
quadlets: quadlets,
|
||||||
|
files: _optionalStringList(map, 'files'),
|
||||||
configureSchema: schemas == null
|
configureSchema: schemas == null
|
||||||
? null
|
? null
|
||||||
: _optionalString(schemas, 'configure'),
|
: _optionalString(schemas, 'configure'),
|
||||||
|
|
@ -246,6 +254,7 @@ class InfraServiceManifest {
|
||||||
'manifest': manifestPath,
|
'manifest': manifestPath,
|
||||||
'units': units,
|
'units': units,
|
||||||
'quadlets': quadlets.map((quadlet) => quadlet.toJson()).toList(),
|
'quadlets': quadlets.map((quadlet) => quadlet.toJson()).toList(),
|
||||||
|
'files': filePaths,
|
||||||
'configure_schema': configureSchemaPath,
|
'configure_schema': configureSchemaPath,
|
||||||
'init_schema': initSchemaPath,
|
'init_schema': initSchemaPath,
|
||||||
'active_config': activeConfigurePath,
|
'active_config': activeConfigurePath,
|
||||||
|
|
@ -311,6 +320,20 @@ List<dynamic> _requiredList(Map<String, dynamic> map, String key) {
|
||||||
throw FormatException('manifest.yaml is missing list "$key".');
|
throw FormatException('manifest.yaml is missing list "$key".');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> _optionalStringList(Map<String, dynamic> map, String key) {
|
||||||
|
final value = map[key];
|
||||||
|
if (value == null) return const [];
|
||||||
|
if (value is! List) {
|
||||||
|
throw FormatException('manifest.yaml field "$key" must be a list.');
|
||||||
|
}
|
||||||
|
return value.map((item) {
|
||||||
|
if (item is String && item.isNotEmpty) return item;
|
||||||
|
throw FormatException(
|
||||||
|
'manifest.yaml field "$key" must contain non-empty strings.',
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
String _requiredString(Map<String, dynamic> map, String key) {
|
String _requiredString(Map<String, dynamic> map, String key) {
|
||||||
final value = _optionalString(map, key);
|
final value = _optionalString(map, key);
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"files": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Additional files installed beside Quadlets, such as Containerfiles for build contexts.",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"pattern": "^[^/].*$"
|
||||||
|
}
|
||||||
|
},
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ void main() {
|
||||||
'install dry-run reports symlink actions without writing files',
|
'install dry-run reports symlink actions without writing files',
|
||||||
() async {
|
() async {
|
||||||
final fs = MemoryFileSystem.test();
|
final fs = MemoryFileSystem.test();
|
||||||
_writeService(fs, includeNetwork: true);
|
_writeService(fs, includeNetwork: true, includeFile: true);
|
||||||
final manifest = await InfraRepository(
|
final manifest = await InfraRepository(
|
||||||
infraDir: '/project/.project/infrastructure',
|
infraDir: '/project/.project/infrastructure',
|
||||||
fs: fs,
|
fs: fs,
|
||||||
|
|
@ -133,6 +133,7 @@ void main() {
|
||||||
|
|
||||||
expect(result.actions.join('\n'), contains('app_postgres.container'));
|
expect(result.actions.join('\n'), contains('app_postgres.container'));
|
||||||
expect(result.actions.join('\n'), contains('app_postgres.network'));
|
expect(result.actions.join('\n'), contains('app_postgres.network'));
|
||||||
|
expect(result.actions.join('\n'), contains('Containerfile'));
|
||||||
expect(
|
expect(
|
||||||
await fs
|
await fs
|
||||||
.link(
|
.link(
|
||||||
|
|
@ -174,6 +175,7 @@ void _writeService(
|
||||||
String serviceId = 'postgres',
|
String serviceId = 'postgres',
|
||||||
String unit = 'app_postgres.service',
|
String unit = 'app_postgres.service',
|
||||||
bool includeNetwork = false,
|
bool includeNetwork = false,
|
||||||
|
bool includeFile = false,
|
||||||
}) {
|
}) {
|
||||||
final serviceDir = fs.directory(
|
final serviceDir = fs.directory(
|
||||||
'/project/.project/infrastructure/services/postgres',
|
'/project/.project/infrastructure/services/postgres',
|
||||||
|
|
@ -188,6 +190,11 @@ void _writeService(
|
||||||
.file('${serviceDir.path}/app_postgres.network')
|
.file('${serviceDir.path}/app_postgres.network')
|
||||||
.writeAsStringSync('[Network]\nNetworkName=app_postgres\n');
|
.writeAsStringSync('[Network]\nNetworkName=app_postgres\n');
|
||||||
}
|
}
|
||||||
|
if (includeFile) {
|
||||||
|
fs
|
||||||
|
.file('${serviceDir.path}/Containerfile')
|
||||||
|
.writeAsStringSync('FROM scratch\n');
|
||||||
|
}
|
||||||
fs
|
fs
|
||||||
.file('${serviceDir.path}/configure.schema.json')
|
.file('${serviceDir.path}/configure.schema.json')
|
||||||
.writeAsStringSync('{"type":"object"}');
|
.writeAsStringSync('{"type":"object"}');
|
||||||
|
|
@ -197,6 +204,13 @@ void _writeService(
|
||||||
final networkQuadlet = includeNetwork
|
final networkQuadlet = includeNetwork
|
||||||
? '''
|
? '''
|
||||||
- file: app_postgres.network
|
- file: app_postgres.network
|
||||||
|
'''
|
||||||
|
: '';
|
||||||
|
final files = includeFile
|
||||||
|
? '''
|
||||||
|
files:
|
||||||
|
- Containerfile
|
||||||
|
|
||||||
'''
|
'''
|
||||||
: '';
|
: '';
|
||||||
fs.file('${serviceDir.path}/manifest.yaml').writeAsStringSync('''
|
fs.file('${serviceDir.path}/manifest.yaml').writeAsStringSync('''
|
||||||
|
|
@ -214,6 +228,7 @@ quadlets:
|
||||||
profiles_dir: app_postgres.profiles.d
|
profiles_dir: app_postgres.profiles.d
|
||||||
$networkQuadlet
|
$networkQuadlet
|
||||||
|
|
||||||
|
$files
|
||||||
schemas:
|
schemas:
|
||||||
configure: configure.schema.json
|
configure: configure.schema.json
|
||||||
init: init.schema.json
|
init: init.schema.json
|
||||||
|
|
@ -234,6 +249,7 @@ Map<String, Object?> _manifestObject() => {
|
||||||
},
|
},
|
||||||
{'file': 'app_postgres.network'},
|
{'file': 'app_postgres.network'},
|
||||||
],
|
],
|
||||||
|
'files': ['Containerfile'],
|
||||||
'schemas': {'configure': 'configure.schema.json', 'init': 'init.schema.json'},
|
'schemas': {'configure': 'configure.schema.json', 'init': 'init.schema.json'},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue