Add support for init field from compose

Init supports boolean values, and is not included in the config when
not defined.

Change-type: patch
Signed-off-by: Christina Ying Wang <christina@balena.io>
This commit is contained in:
Christina Ying Wang 2024-09-09 14:39:42 -07:00 committed by Felipe Lalanne
parent e01aaaaafb
commit ed1c18e369
No known key found for this signature in database
GPG Key ID: 03E696BFD472B26A
7 changed files with 412 additions and 3 deletions

View File

@ -337,6 +337,13 @@ class ServiceImpl implements Service {
config.tty = Boolean(config.tty);
}
// Only keep init field if it's a boolean
if (config.init != null) {
config.init = Boolean(config.init);
} else {
delete config.init;
}
if (Array.isArray(config.sysctls)) {
config.sysctls = _.fromPairs(
_.map(config.sysctls, (v) => _.split(v, '=')),
@ -598,6 +605,11 @@ class ServiceImpl implements Service {
tty: container.Config.Tty || false,
};
// Only add `init` if true or false, otherwise leave blank
if (typeof container.HostConfig.Init === 'boolean') {
svc.config.init = container.HostConfig.Init;
}
const appId = checkInt(svc.config.labels['io.balena.app-id']);
if (appId == null) {
throw new InternalInconsistencyError(
@ -739,6 +751,7 @@ class ServiceImpl implements Service {
UsernsMode: this.config.usernsMode,
NanoCpus: this.config.cpus,
IpcMode: this.config.ipc,
Init: this.config.init,
} as Dockerode.ContainerCreateOptions['HostConfig'],
Healthcheck: ComposeUtils.serviceHealthcheckToDockerHealthcheck(
this.config.healthcheck,

View File

@ -183,7 +183,7 @@ export interface ServiceComposeConfig {
groupAdd?: string[];
healthcheck?: ComposeHealthcheck;
image: string;
init?: string | boolean;
init?: boolean;
labels?: { [labelName: string]: string };
running?: boolean;
networkMode?: string;
@ -272,6 +272,7 @@ export interface ServiceConfig {
domainname: string;
hostname: string;
ipc: string;
init?: boolean;
macAddress: string;
memLimit: number;
memReservation: number;

View File

@ -105,8 +105,7 @@
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"Init": false
"IOMaximumBandwidth": 0
},
"GraphDriver": {
"Data": null,

View File

@ -0,0 +1,17 @@
{
"imageId": 478890,
"serviceName": "main",
"image": "sha256:7f54fa690ce19a1f625b04479ae1f12f44d36112a74be7edfefa777ecfdb194b",
"running": true,
"environment": {},
"labels": {},
"appId": 1011165,
"appUuid": "aaaaaaaa",
"releaseId": 597007,
"serviceId": 43697,
"commit": "ff300a701054ac15281de1f9c0e84b8c",
"imageName": "registry2.resin.io/v2/bf9c649a5ac2fe147bbe350875042388@sha256:3a5c17b715b4f8265539c1a006dd1abdd2ff3b758aa23df99f77c792f40c3d43",
"composition": {
"init": true
}
}

View File

@ -0,0 +1,103 @@
{
"serviceName": "main",
"imageInfo": {
"Id": "sha256:7f54fa690ce19a1f625b04479ae1f12f44d36112a74be7edfefa777ecfdb194b",
"RepoTags": [],
"RepoDigests": [
"registry2.resin.io/v2/90e3bf4c3dc1e59221b7b3e659a327f6@sha256:3a5c17b715b4f8265539c1a006dd1abdd2ff3b758aa23df99f77c792f40c3d43"
],
"Parent": "",
"Comment": "",
"Created": "2018-09-12T13:00:43.974720835Z",
"Container": "07cb0400e218ae235e67cf1fd283dc09559f57fbe2a36f5cc89302388371781c",
"ContainerConfig": {
"Hostname": "137f767087a2",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/bin/sh\" \"-c\" \"while true; do echo 'hello'; sleep 5; done;\"]"
],
"ArgsEscaped": true,
"Image": "sha256:8d68949dbddcb3ab1a61caeffa0aa1a6e27425ecc4f7665d04d8d0e5bfa03298",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": [],
"Labels": {}
},
"DockerVersion": "17.05.0-ce",
"Author": "",
"Config": {
"Hostname": "137f767087a2",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"while true; do echo 'hello'; sleep 5; done;"
],
"ArgsEscaped": true,
"Image": "sha256:8d68949dbddcb3ab1a61caeffa0aa1a6e27425ecc4f7665d04d8d0e5bfa03298",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": [],
"Labels": {}
},
"Architecture": "arm64",
"Os": "linux",
"Size": 104966431,
"VirtualSize": 104966431,
"GraphDriver": {
"Data": null,
"Name": "aufs"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:a3075e9def48840598abcfe08c1ee564c989d1014d847082d950dca2c94098ec",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
},
"appName": "supervisortest",
"supervisorApiHost": "172.17.0.1",
"hostPathExists": {
"firmware": true,
"modules": true
},
"hostname": "7dadabd",
"uuid": "a7feb967fac7f559ccf2a006a36bcf5d",
"listenPort": "48484",
"name": "Office",
"apiSecret": "d4bf8369519c32adaa5dd1f84367aa817403f2a3ce976be9c9bacd4d344fdd",
"deviceApiKey": "ff89e1d8db58a7ca52a435f2adea319a",
"version": "7.18.0",
"deviceArch": "amd64",
"deviceType": "raspberrypi3",
"osVersion": "Resin OS 2.13.6+rev1"
}

View File

@ -0,0 +1,232 @@
{
"Id": "52cfd7a64d50236376741dd2578c4fbb0178d90e2e4fae55f3e14cd905e9ac9e",
"Created": "2018-09-12T14:38:42.696028995Z",
"Path": "/bin/sh",
"Args": [
"-c",
"while true; do echo 'hello'; sleep 5; done;"
],
"State": {
"Status": "exited",
"Running": false,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 0,
"ExitCode": 137,
"Error": "",
"StartedAt": "2018-09-12T14:38:45.408574694Z",
"FinishedAt": "2018-09-12T14:38:46.462783621Z"
},
"Image": "sha256:7f54fa690ce19a1f625b04479ae1f12f44d36112a74be7edfefa777ecfdb194b",
"ResolvConfPath": "/var/lib/docker/containers/52cfd7a64d50236376741dd2578c4fbb0178d90e2e4fae55f3e14cd905e9ac9e/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/52cfd7a64d50236376741dd2578c4fbb0178d90e2e4fae55f3e14cd905e9ac9e/hostname",
"HostsPath": "/var/lib/docker/containers/52cfd7a64d50236376741dd2578c4fbb0178d90e2e4fae55f3e14cd905e9ac9e/hosts",
"LogPath": "",
"Name": "main_1_1",
"RestartCount": 0,
"Driver": "aufs",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": [
"/tmp/balena-supervisor/services/1011165/main:/tmp/resin",
"/tmp/balena-supervisor/services/1011165/main:/tmp/balena"
],
"ContainerIDFile": "",
"LogConfig": {
"Type": "journald",
"Config": {}
},
"NetworkMode": "aaaaaaaa_default",
"PortBindings": {},
"RestartPolicy": {
"Name": "always",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": [],
"CapDrop": [],
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": [],
"GroupAdd": [],
"IpcMode": "shareable",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": [],
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": null,
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DiskQuota": 0,
"KernelMemory": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": -1,
"OomKillDisable": false,
"PidsLimit": 0,
"Ulimits": [],
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"Init": true
},
"GraphDriver": {
"Data": null,
"Name": "aufs"
},
"Mounts": [
{
"Type": "bind",
"Source": "/tmp/balena-supervisor/services/1011165/main",
"Destination": "/tmp/resin",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "bind",
"Source": "/tmp/balena-supervisor/services/1011165/main",
"Destination": "/tmp/balena",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
"Config": {
"Hostname": "52cfd7a64d50",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": true,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"RESIN_APP_ID=1011165",
"RESIN_APP_UUID=aaaaaaaa",
"RESIN_APP_NAME=supervisortest",
"RESIN_SERVICE_NAME=main",
"RESIN_DEVICE_UUID=a7feb967fac7f559ccf2a006a36bcf5d",
"RESIN_DEVICE_ARCH=amd64",
"RESIN_DEVICE_TYPE=raspberrypi3",
"RESIN_HOST_OS_VERSION=Resin OS 2.13.6+rev1",
"RESIN_APP_LOCK_PATH=/tmp/balena/updates.lock",
"RESIN_SERVICE_KILL_ME_PATH=/tmp/balena/handover-complete",
"RESIN=1",
"BALENA_APP_ID=1011165",
"BALENA_APP_UUID=aaaaaaaa",
"BALENA_APP_NAME=supervisortest",
"BALENA_SERVICE_NAME=main",
"BALENA_DEVICE_UUID=a7feb967fac7f559ccf2a006a36bcf5d",
"BALENA_DEVICE_ARCH=amd64",
"BALENA_DEVICE_TYPE=raspberrypi3",
"BALENA_HOST_OS_VERSION=Resin OS 2.13.6+rev1",
"BALENA_APP_LOCK_PATH=/tmp/balena/updates.lock",
"BALENA_SERVICE_HANDOVER_COMPLETE_PATH=/tmp/balena/handover-complete",
"BALENA=1",
"USER=root",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"while true; do echo 'hello'; sleep 5; done;"
],
"Healthcheck": {
"Test": [
"NONE"
]
},
"Image": "sha256:7f54fa690ce19a1f625b04479ae1f12f44d36112a74be7edfefa777ecfdb194b",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
"io.resin.app-id": "1011165",
"io.balena.app-uuid": "aaaaaaaa",
"io.resin.service-id": "43697",
"io.resin.service-name": "main",
"io.resin.supervised": "true"
},
"StopTimeout": 0
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "bf4952b7f6695a8f05da1807946723b37e1041b8f41588678d6dece310270990",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "/var/run/balena/netns/bf4952b7f669",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": "",
"Networks": {
"aaaaaaaa_default": {
"IPAMConfig": {},
"Links": null,
"Aliases": [
"main",
"52cfd7a64d50"
],
"NetworkID": "f88716ed3d340f1b9aa61df22d92ce6ad8aa752d8bf8e4aa6e74142dea677465",
"EndpointID": "",
"Gateway": "",
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "",
"DriverOpts": null
}
}
}
}

View File

@ -24,6 +24,11 @@ const configs = {
imageInfo: require('~/test-data/docker-states/network-mode-service/imageInfo.json'),
inspect: require('~/test-data/docker-states/network-mode-service/inspect.json'),
},
init: {
compose: require('~/test-data/docker-states/init/compose.json'),
imageInfo: require('~/test-data/docker-states/init/imageInfo.json'),
inspect: require('~/test-data/docker-states/init/inspect.json'),
},
};
describe('compose/service: unit tests', () => {
@ -263,6 +268,31 @@ describe('compose/service: unit tests', () => {
]);
});
it('should support init property', async () => {
const appConfigWithInit = (init?: boolean) => ({
appId: 123,
serviceId: 123,
serviceName: 'test',
composition: {
init,
},
});
const svc = await Service.fromComposeObject(appConfigWithInit(true), {
appName: 'test',
} as any);
expect(svc.config).to.have.property('init').that.equals(true);
const svc2 = await Service.fromComposeObject(appConfigWithInit(false), {
appName: 'test',
} as any);
expect(svc2.config).to.have.property('init').that.equals(false);
const svc3 = await Service.fromComposeObject(appConfigWithInit(), {
appName: 'test',
} as any);
expect(svc3.config).to.not.have.property('init');
});
describe('Parsing memory strings from compose configuration', () => {
const makeComposeServiceWithLimit = async (memLimit?: string | number) =>
await Service.fromComposeObject(
@ -898,6 +928,20 @@ describe('compose/service: unit tests', () => {
expect(dockerSvc.isEqualConfig(composeSvc, {})).to.equals(true);
});
it('should correctly handle the init config', async () => {
const composeSvc = await Service.fromComposeObject(
configs.init.compose,
configs.init.imageInfo,
);
const dockerSvc = Service.fromDockerContainer(configs.init.inspect);
const composeConfig = omitConfigForComparison(composeSvc.config);
const dockerConfig = omitConfigForComparison(dockerSvc.config);
expect(composeConfig).to.deep.equal(dockerConfig);
expect(dockerSvc.isEqualConfig(composeSvc, {})).to.equals(true);
});
describe('Networks', () => {
it('should correctly convert Docker format to service format', () => {
const { inspectInfo } = createContainer({