mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-20 14:13:08 +00:00
Merge pull request #1036 from balena-io/851-service-network-mode
Support network_modes of service:<servicename>
This commit is contained in:
commit
5e73d4d9a3
@ -317,7 +317,7 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
# Compares current and target services and returns a list of service pairs to be updated/removed/installed.
|
||||
# The returned list is an array of objects where the "current" and "target" properties define the update pair, and either can be null
|
||||
# (in the case of an install or removal).
|
||||
compareServicesForUpdate: (currentServices, targetServices) =>
|
||||
compareServicesForUpdate: (currentServices, targetServices, containerIds) =>
|
||||
removePairs = []
|
||||
installPairs = []
|
||||
updatePairs = []
|
||||
@ -366,13 +366,13 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
# already started it before. In this case it means it just exited so we don't want to start it again.
|
||||
alreadyStarted = (serviceId) =>
|
||||
return (
|
||||
currentServicesPerId[serviceId].isEqualExceptForRunningState(targetServicesPerId[serviceId]) and
|
||||
currentServicesPerId[serviceId].isEqualExceptForRunningState(targetServicesPerId[serviceId], containerIds) and
|
||||
targetServicesPerId[serviceId].config.running and
|
||||
@_containerStarted[currentServicesPerId[serviceId].containerId]
|
||||
)
|
||||
|
||||
needUpdate = _.filter toBeMaybeUpdated, (serviceId) ->
|
||||
!currentServicesPerId[serviceId].isEqual(targetServicesPerId[serviceId]) and !alreadyStarted(serviceId)
|
||||
!currentServicesPerId[serviceId].isEqual(targetServicesPerId[serviceId], containerIds) and !alreadyStarted(serviceId)
|
||||
|
||||
for serviceId in needUpdate
|
||||
updatePairs.push({
|
||||
@ -535,7 +535,7 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
return { action: 'noop' }
|
||||
}
|
||||
|
||||
_nextStepForService: ({ current, target }, updateContext, localMode) =>
|
||||
_nextStepForService: ({ current, target }, updateContext, localMode, containerIds) =>
|
||||
{ targetApp, networkPairs, volumePairs, installPairs, updatePairs, availableImages, downloading } = updateContext
|
||||
if current?.status == 'Stopping'
|
||||
# There is already a kill step in progress for this service, so we wait
|
||||
@ -564,7 +564,7 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
# even if its strategy is handover
|
||||
needsSpecialKill = @_hasCurrentNetworksOrVolumes(current, networkPairs, volumePairs)
|
||||
|
||||
if current?.isEqualConfig(target)
|
||||
if current?.isEqualConfig(target, containerIds)
|
||||
# We're only stopping/starting it
|
||||
return @_updateContainerStep(current, target)
|
||||
else if !current?
|
||||
@ -578,7 +578,7 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
timeout = checkInt(target.config.labels['io.balena.update.handover-timeout'])
|
||||
return @_strategySteps[strategy](current, target, needsDownload, dependenciesMetForStart, dependenciesMetForKill, needsSpecialKill, timeout)
|
||||
|
||||
_nextStepsForAppUpdate: (currentApp, targetApp, localMode, availableImages = [], downloading = []) =>
|
||||
_nextStepsForAppUpdate: (currentApp, targetApp, localMode, containerIds, availableImages = [], downloading = []) =>
|
||||
emptyApp = { services: [], volumes: {}, networks: {} }
|
||||
if !targetApp?
|
||||
targetApp = emptyApp
|
||||
@ -599,7 +599,7 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
appId = targetApp.appId ? currentApp.appId
|
||||
networkPairs = @compareNetworksForUpdate({ current: currentApp.networks, target: targetApp.networks }, appId)
|
||||
volumePairs = @compareVolumesForUpdate({ current: currentApp.volumes, target: targetApp.volumes }, appId)
|
||||
{ removePairs, installPairs, updatePairs } = @compareServicesForUpdate(currentApp.services, targetApp.services)
|
||||
{ removePairs, installPairs, updatePairs } = @compareServicesForUpdate(currentApp.services, targetApp.services, containerIds)
|
||||
steps = []
|
||||
# All removePairs get a 'kill' action
|
||||
for pair in removePairs
|
||||
@ -611,7 +611,7 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
# next step for install pairs in download - start order, but start requires dependencies, networks and volumes met
|
||||
# next step for update pairs in order by update strategy. start requires dependencies, networks and volumes met.
|
||||
for pair in installPairs.concat(updatePairs)
|
||||
step = @_nextStepForService(pair, { targetApp, networkPairs, volumePairs, installPairs, updatePairs, availableImages, downloading }, localMode)
|
||||
step = @_nextStepForService(pair, { targetApp, networkPairs, volumePairs, installPairs, updatePairs, availableImages, downloading }, localMode, containerIds)
|
||||
if step?
|
||||
steps.push(step)
|
||||
# next step for network pairs - remove requires services killed, create kill if no pairs or steps affect that service
|
||||
@ -858,7 +858,7 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
return notUsedForDelta and notUsedByProxyvisor
|
||||
return { imagesToSave, imagesToRemove }
|
||||
|
||||
_inferNextSteps: (cleanupNeeded, availableImages, downloading, supervisorNetworkReady, current, target, ignoreImages, { localMode, delta }) =>
|
||||
_inferNextSteps: (cleanupNeeded, availableImages, downloading, supervisorNetworkReady, current, target, ignoreImages, { localMode, delta }, containerIds) =>
|
||||
volumePromises = []
|
||||
Promise.try =>
|
||||
if localMode
|
||||
@ -919,7 +919,7 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
if _.isEmpty(nextSteps)
|
||||
allAppIds = _.union(_.keys(currentByAppId), _.keys(targetByAppId))
|
||||
for appId in allAppIds
|
||||
nextSteps = nextSteps.concat(@_nextStepsForAppUpdate(currentByAppId[appId], targetByAppId[appId], localMode, availableImages, downloading))
|
||||
nextSteps = nextSteps.concat(@_nextStepsForAppUpdate(currentByAppId[appId], targetByAppId[appId], localMode, containerIds[appId], availableImages, downloading))
|
||||
if oldApps != null and _.includes(oldApps, checkInt(appId))
|
||||
# We check if everything else has been done for
|
||||
# the old app to be removed. If it has, we then
|
||||
@ -965,7 +965,16 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
return Promise.reject(new Error("Invalid action #{step.action}"))
|
||||
@actionExecutors[step.action](step, { force, skipLock })
|
||||
|
||||
getExtraStateForComparison: =>
|
||||
getExtraStateForComparison: (currentState, targetState) =>
|
||||
containerIdsByAppId = {}
|
||||
_(currentState.local.apps)
|
||||
.keys()
|
||||
.concat(_.keys(targetState.local.apps))
|
||||
.uniq()
|
||||
.each (id) =>
|
||||
intId = checkInt(id)
|
||||
containerIdsByAppId[intId] = @services.getContainerIdMap(intId)
|
||||
|
||||
@config.get('localMode').then (localMode) =>
|
||||
Promise.props({
|
||||
cleanupNeeded: @images.isCleanupNeeded()
|
||||
@ -973,15 +982,16 @@ module.exports = class ApplicationManager extends EventEmitter
|
||||
downloading: @images.getDownloadingImageIds()
|
||||
supervisorNetworkReady: @networks.supervisorNetworkReady()
|
||||
delta: @config.get('delta')
|
||||
containerIds: Promise.props(containerIdsByAppId)
|
||||
localMode
|
||||
})
|
||||
|
||||
getRequiredSteps: (currentState, targetState, extraState, ignoreImages = false) =>
|
||||
{ cleanupNeeded, availableImages, downloading, supervisorNetworkReady, delta, localMode } = extraState
|
||||
{ cleanupNeeded, availableImages, downloading, supervisorNetworkReady, delta, localMode, containerIds } = extraState
|
||||
conf = { delta, localMode }
|
||||
if conf.localMode
|
||||
cleanupNeeded = false
|
||||
@_inferNextSteps(cleanupNeeded, availableImages, downloading, supervisorNetworkReady, currentState, targetState, ignoreImages, conf)
|
||||
@_inferNextSteps(cleanupNeeded, availableImages, downloading, supervisorNetworkReady, currentState, targetState, ignoreImages, conf, containerIds)
|
||||
.then (nextSteps) =>
|
||||
if ignoreImages and _.some(nextSteps, action: 'fetch')
|
||||
throw new Error('Cannot fetch images while executing an API action')
|
||||
|
@ -93,9 +93,13 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
||||
}
|
||||
|
||||
public async get(service: Service) {
|
||||
// Get the container ids for special network handling
|
||||
const containerIds = await this.getContainerIdMap(service.appId!);
|
||||
const services = (await this.getAll(
|
||||
`service-id=${service.serviceId}`,
|
||||
)).filter(currentService => currentService.isEqualConfig(service));
|
||||
)).filter(currentService =>
|
||||
currentService.isEqualConfig(service, containerIds),
|
||||
);
|
||||
|
||||
if (services.length === 0) {
|
||||
const e: StatusCodeError = new Error(
|
||||
@ -266,7 +270,17 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
||||
);
|
||||
}
|
||||
|
||||
const conf = service.toDockerContainer({ deviceName });
|
||||
// Get all created services so far
|
||||
if (service.appId == null) {
|
||||
throw new InternalInconsistencyError(
|
||||
'Attempt to start a service without an existing application ID',
|
||||
);
|
||||
}
|
||||
const serviceContainerIds = await this.getContainerIdMap(service.appId);
|
||||
const conf = service.toDockerContainer({
|
||||
deviceName,
|
||||
containerIds: serviceContainerIds,
|
||||
});
|
||||
const nets = serviceNetworksToDockerNetworks(
|
||||
service.extraNetworksToJoin(),
|
||||
);
|
||||
@ -494,6 +508,13 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
||||
}
|
||||
}
|
||||
|
||||
public async getContainerIdMap(appId: number): Promise<Dictionary<string>> {
|
||||
return _(await this.getAllByAppId(appId))
|
||||
.keyBy('serviceName')
|
||||
.mapValues('containerId')
|
||||
.value() as Dictionary<string>;
|
||||
}
|
||||
|
||||
private reportChange(containerId?: string, status?: Partial<Service>) {
|
||||
if (containerId != null) {
|
||||
if (status != null) {
|
||||
|
@ -23,6 +23,9 @@ import { sanitiseComposeConfig } from './sanitise';
|
||||
|
||||
import log from '../lib/supervisor-console';
|
||||
|
||||
const SERVICE_NETWORK_MODE_REGEX = /service:\s*(.+)/;
|
||||
const CONTAINER_NETWORK_MODE_REGEX = /container:\s*(.+)/;
|
||||
|
||||
export class Service {
|
||||
public appId: number | null;
|
||||
public imageId: number | null;
|
||||
@ -33,7 +36,7 @@ export class Service {
|
||||
public imageName: string | null;
|
||||
public containerId: string | null;
|
||||
|
||||
public dependsOn: string | null;
|
||||
public dependsOn: string[] | null;
|
||||
|
||||
public status: string;
|
||||
public createdAt: Date | null;
|
||||
@ -63,6 +66,9 @@ export class Service {
|
||||
// reported on a container inspect, so we cannot use it
|
||||
// to compare containers
|
||||
'cpus',
|
||||
// These fields are special case, due to network_mode:service:<service>
|
||||
'networkMode',
|
||||
'hostname',
|
||||
].concat(Service.configArrayFields);
|
||||
|
||||
private constructor() {}
|
||||
@ -137,21 +143,6 @@ export class Service {
|
||||
});
|
||||
delete config.networks;
|
||||
|
||||
// Check for unsupported networkMode entries
|
||||
if (config.networkMode != null) {
|
||||
if (/service:(\s*)?.+/.test(config.networkMode)) {
|
||||
log.warn(
|
||||
'A network_mode referencing a service is not yet supported. Ignoring.',
|
||||
);
|
||||
delete config.networkMode;
|
||||
} else if (/container:(\s*)?.+/.test(config.networkMode)) {
|
||||
log.warn(
|
||||
'A network_mode referencing a container is not supported. Ignoring.',
|
||||
);
|
||||
delete config.networkMode;
|
||||
}
|
||||
}
|
||||
|
||||
// memory strings
|
||||
const memLimit = ComposeUtils.parseMemoryNumber(config.memLimit, '0');
|
||||
const memReservation = ComposeUtils.parseMemoryNumber(
|
||||
@ -190,8 +181,27 @@ export class Service {
|
||||
config.dnsSearch = [config.dnsSearch];
|
||||
}
|
||||
|
||||
// Assign network_mode to a default value if necessary
|
||||
if (!config.networkMode) {
|
||||
// Special case network modes
|
||||
let serviceNetworkMode = false;
|
||||
if (config.networkMode != null) {
|
||||
const match = config.networkMode.match(SERVICE_NETWORK_MODE_REGEX);
|
||||
if (match != null) {
|
||||
// We need to add a depends on here to ensure that
|
||||
// the needed container has started up by the time
|
||||
// we try to start this service
|
||||
if (service.dependsOn == null) {
|
||||
service.dependsOn = [];
|
||||
}
|
||||
service.dependsOn.push(match[1]);
|
||||
serviceNetworkMode = true;
|
||||
} else if (CONTAINER_NETWORK_MODE_REGEX.test(config.networkMode)) {
|
||||
log.warn(
|
||||
'A network_mode referencing a container is not supported. Ignoring.',
|
||||
);
|
||||
delete config.networkMode;
|
||||
}
|
||||
} else {
|
||||
// Assign network_mode to a default value if necessary
|
||||
if (!_.isEmpty(networks)) {
|
||||
config.networkMode = _.keys(networks)[0];
|
||||
} else {
|
||||
@ -203,7 +213,7 @@ export class Service {
|
||||
config.networkMode !== 'bridge' &&
|
||||
config.networkMode !== 'none'
|
||||
) {
|
||||
if (networks[config.networkMode] == null) {
|
||||
if (networks[config.networkMode!] == null && !serviceNetworkMode) {
|
||||
// The network mode has not been set explicitly
|
||||
config.networkMode = `${service.appId}_${config.networkMode}`;
|
||||
// If we don't have any networks, we need to
|
||||
@ -551,6 +561,7 @@ export class Service {
|
||||
|
||||
public toDockerContainer(opts: {
|
||||
deviceName: string;
|
||||
containerIds: Dictionary<string>;
|
||||
}): Dockerode.ContainerCreateOptions {
|
||||
const { binds, volumes } = this.getBindsAndVolumes();
|
||||
const { exposedPorts, portBindings } = this.generateExposeAndPorts();
|
||||
@ -565,6 +576,16 @@ export class Service {
|
||||
(_v, k) => k === this.config.networkMode,
|
||||
) as ServiceConfig['networks'];
|
||||
|
||||
const match = this.config.networkMode.match(SERVICE_NETWORK_MODE_REGEX);
|
||||
if (match != null) {
|
||||
const containerId = opts.containerIds[match[1]];
|
||||
if (!containerId) {
|
||||
throw new Error(
|
||||
`No container for network_mode: 'service: ${match[1]}'`,
|
||||
);
|
||||
}
|
||||
this.config.networkMode = `container:${containerId}`;
|
||||
}
|
||||
return {
|
||||
name: `${this.serviceName}_${this.imageId}_${this.releaseId}`,
|
||||
Tty: this.config.tty,
|
||||
@ -642,7 +663,10 @@ export class Service {
|
||||
};
|
||||
}
|
||||
|
||||
public isEqualConfig(service: Service): boolean {
|
||||
public isEqualConfig(
|
||||
service: Service,
|
||||
currentContainerIds: Dictionary<string>,
|
||||
): boolean {
|
||||
// Check all of the networks for any changes
|
||||
let sameNetworks = true;
|
||||
_.each(service.config.networks, (network, name) => {
|
||||
@ -702,24 +726,73 @@ export class Service {
|
||||
log.debug(' Network changes detected');
|
||||
}
|
||||
}
|
||||
return sameNetworks && sameConfig;
|
||||
|
||||
// Check the network mode separetely, as if it is a
|
||||
// service: network mode, the container id needs to be
|
||||
// checked against the running containers
|
||||
|
||||
// When this function is called, it's with the current
|
||||
// state as a parameter and the target as the instance.
|
||||
// We shouldn't rely on that because it's not enforced
|
||||
// anywhere. For that reason we need to consider both
|
||||
// network_modes in the correct way
|
||||
let sameNetworkMode = false;
|
||||
for (const [a, b] of [
|
||||
[this.config.networkMode, service.config.networkMode],
|
||||
[service.config.networkMode, this.config.networkMode],
|
||||
]) {
|
||||
const aMatch = a.match(SERVICE_NETWORK_MODE_REGEX);
|
||||
const bMatch = b.match(SERVICE_NETWORK_MODE_REGEX);
|
||||
|
||||
if (aMatch !== null) {
|
||||
if (bMatch === null) {
|
||||
const containerMatch = b.match(CONTAINER_NETWORK_MODE_REGEX);
|
||||
if (
|
||||
containerMatch !== null &&
|
||||
currentContainerIds[aMatch[1]] === containerMatch[1]
|
||||
) {
|
||||
sameNetworkMode = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// They're both service entries, we shouldn't get here
|
||||
// but it's technically an equal configuration
|
||||
if (a === b) {
|
||||
sameNetworkMode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (a === b && this.config.hostname === service.config.hostname) {
|
||||
// We consider the hostname when it's not a service: entry
|
||||
sameNetworkMode = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sameNetworks && sameConfig && sameNetworkMode;
|
||||
}
|
||||
|
||||
public extraNetworksToJoin(): ServiceConfig['networks'] {
|
||||
return _.omit(this.config.networks, this.config.networkMode);
|
||||
}
|
||||
|
||||
public isEqualExceptForRunningState(service: Service): boolean {
|
||||
public isEqualExceptForRunningState(
|
||||
service: Service,
|
||||
currentContainerIds: Dictionary<string>,
|
||||
): boolean {
|
||||
return (
|
||||
this.isEqualConfig(service) &&
|
||||
this.isEqualConfig(service, currentContainerIds) &&
|
||||
this.releaseId === service.releaseId &&
|
||||
this.imageId === service.imageId
|
||||
);
|
||||
}
|
||||
|
||||
public isEqual(service: Service): boolean {
|
||||
public isEqual(
|
||||
service: Service,
|
||||
currentContainerIds: Dictionary<string>,
|
||||
): boolean {
|
||||
return (
|
||||
this.isEqualExceptForRunningState(service) &&
|
||||
this.isEqualExceptForRunningState(service, currentContainerIds) &&
|
||||
this.config.running === service.config.running
|
||||
);
|
||||
}
|
||||
|
@ -605,13 +605,13 @@ module.exports = class DeviceState extends EventEmitter
|
||||
@applications.localModeSwitchCompletion()
|
||||
.then =>
|
||||
@usingInferStepsLock =>
|
||||
@applications.getExtraStateForComparison()
|
||||
.then (extraState) =>
|
||||
Promise.all([
|
||||
@getCurrentForComparison()
|
||||
@getTarget({ initial, intermediate })
|
||||
])
|
||||
.then ([ currentState, targetState ]) =>
|
||||
Promise.all([
|
||||
@getCurrentForComparison()
|
||||
@getTarget({ initial, intermediate })
|
||||
])
|
||||
.then ([ currentState, targetState ]) =>
|
||||
@applications.getExtraStateForComparison(currentState, targetState)
|
||||
.then (extraState) =>
|
||||
@deviceConfig.getRequiredSteps(currentState, targetState)
|
||||
.then (deviceConfigSteps) =>
|
||||
noConfigSteps = _.every(deviceConfigSteps, ({ action }) -> action is 'noop')
|
||||
|
@ -14,6 +14,11 @@ configs = {
|
||||
compose: require('./data/docker-states/entrypoint/compose.json')
|
||||
imageInfo: require('./data/docker-states/entrypoint/imageInfo.json')
|
||||
inspect: require('./data/docker-states/entrypoint/inspect.json')
|
||||
},
|
||||
networkModeService: {
|
||||
compose: require('./data/docker-states/network-mode-service/compose.json')
|
||||
imageInfo: require('./data/docker-states/network-mode-service/imageInfo.json')
|
||||
inspect: require('./data/docker-states/network-mode-service/inspect.json')
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,3 +380,74 @@ describe 'compose/service', ->
|
||||
aliases: [ 'test', '1123' ]
|
||||
}
|
||||
})
|
||||
|
||||
describe 'Network mode=service:', ->
|
||||
it 'should correctly add a depends_on entry for the service', ->
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
network_mode: 'service: test'
|
||||
}, { appName: 'test' })
|
||||
|
||||
expect(s.dependsOn).to.deep.equal(['test'])
|
||||
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
depends_on: [
|
||||
'another_service'
|
||||
]
|
||||
network_mode: 'service: test'
|
||||
}, { appName: 'test' })
|
||||
|
||||
expect(s.dependsOn).to.deep.equal([
|
||||
'another_service',
|
||||
'test'
|
||||
])
|
||||
|
||||
it 'should correctly convert a network_mode service: to a container:', ->
|
||||
s = Service.fromComposeObject({
|
||||
appId: '1234'
|
||||
serviceName: 'foo'
|
||||
releaseId: 2
|
||||
serviceId: 3
|
||||
imageId: 4
|
||||
network_mode: 'service: test'
|
||||
}, { appName: 'test' })
|
||||
expect(s.toDockerContainer({ deviceName: '', containerIds: { test: 'abcdef' } }))
|
||||
.to.have.property('HostConfig')
|
||||
.that.has.property('NetworkMode')
|
||||
.that.equals('container:abcdef')
|
||||
|
||||
it 'should not cause a container restart if a service: container has not changed', ->
|
||||
composeSvc = Service.fromComposeObject(configs.networkModeService.compose, configs.networkModeService.imageInfo)
|
||||
dockerSvc = Service.fromDockerContainer(configs.networkModeService.inspect)
|
||||
|
||||
composeConfig = omitConfigForComparison(composeSvc.config)
|
||||
dockerConfig = omitConfigForComparison(dockerSvc.config)
|
||||
expect(composeConfig).to.not.deep.equal(dockerConfig)
|
||||
|
||||
expect(dockerSvc.isEqualConfig(
|
||||
composeSvc,
|
||||
{ test: 'abcdef' }
|
||||
)).to.be.true
|
||||
|
||||
it 'should restart a container when its dependent network mode container changes', ->
|
||||
composeSvc = Service.fromComposeObject(configs.networkModeService.compose, configs.networkModeService.imageInfo)
|
||||
dockerSvc = Service.fromDockerContainer(configs.networkModeService.inspect)
|
||||
|
||||
composeConfig = omitConfigForComparison(composeSvc.config)
|
||||
dockerConfig = omitConfigForComparison(dockerSvc.config)
|
||||
expect(composeConfig).to.not.deep.equal(dockerConfig)
|
||||
|
||||
expect(dockerSvc.isEqualConfig(
|
||||
composeSvc,
|
||||
{ test: 'qwerty' }
|
||||
)).to.be.false
|
||||
|
||||
|
@ -141,6 +141,7 @@ describe 'ApplicationManager', ->
|
||||
}
|
||||
})
|
||||
stub(@applications.docker, 'getNetworkGateway').returns(Promise.resolve('172.17.0.1'))
|
||||
stub(@applications.docker, 'listContainers').returns(Promise.resolve([]))
|
||||
stub(Service, 'extendEnvVars').callsFake (env) ->
|
||||
env['ADDITIONAL_ENV_VAR'] = 'foo'
|
||||
return env
|
||||
@ -191,6 +192,7 @@ describe 'ApplicationManager', ->
|
||||
after ->
|
||||
@applications.images.inspectByName.restore()
|
||||
@applications.docker.getNetworkGateway.restore()
|
||||
@applications.docker.listContainers.restore()
|
||||
Service.extendEnvVars.restore()
|
||||
|
||||
it 'infers a start step when all that changes is a running state', ->
|
||||
@ -198,7 +200,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[0])
|
||||
@normaliseTarget(targetState[0], availableImages[0])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {}, {})
|
||||
expect(steps).to.eventually.deep.equal([{
|
||||
action: 'start'
|
||||
current: current.local.apps['1234'].services[1]
|
||||
@ -214,7 +216,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[0])
|
||||
@normaliseTarget(targetState[1], availableImages[0])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {}, {})
|
||||
expect(steps).to.eventually.deep.equal([{
|
||||
action: 'kill'
|
||||
current: current.local.apps['1234'].services[1]
|
||||
@ -230,7 +232,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[0])
|
||||
@normaliseTarget(targetState[2], availableImages[0])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {}, {})
|
||||
expect(steps).to.eventually.deep.equal([{
|
||||
action: 'fetch'
|
||||
image: @applications.imageForService(target.local.apps['1234'].services[1])
|
||||
@ -245,7 +247,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[0])
|
||||
@normaliseTarget(targetState[2], availableImages[0])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [ target.local.apps['1234'].services[1].imageId ], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [ target.local.apps['1234'].services[1].imageId ], true, current, target, false, {}, {})
|
||||
expect(steps).to.eventually.deep.equal([{ action: 'noop', appId: 1234 }])
|
||||
)
|
||||
|
||||
@ -254,7 +256,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[0])
|
||||
@normaliseTarget(targetState[3], availableImages[0])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {}, {})
|
||||
expect(steps).to.eventually.deep.equal([{
|
||||
action: 'kill'
|
||||
current: current.local.apps['1234'].services[1]
|
||||
@ -270,7 +272,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[4])
|
||||
@normaliseTarget(targetState[4], availableImages[2])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[2], [], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[2], [], true, current, target, false, {}, {})
|
||||
expect(steps).to.eventually.have.deep.members([{
|
||||
action: 'fetch'
|
||||
image: @applications.imageForService(target.local.apps['1234'].services[0])
|
||||
@ -285,7 +287,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[0])
|
||||
@normaliseTarget(targetState[5], availableImages[1])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[1], [], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[1], [], true, current, target, false, {}, {})
|
||||
expect(steps).to.eventually.have.deep.members([
|
||||
{
|
||||
action: 'kill'
|
||||
@ -311,7 +313,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[1])
|
||||
@normaliseTarget(targetState[4], availableImages[1])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[1], [], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[1], [], true, current, target, false, {}, {})
|
||||
expect(steps).to.eventually.have.deep.members([
|
||||
{
|
||||
action: 'start'
|
||||
@ -329,7 +331,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[2])
|
||||
@normaliseTarget(targetState[4], availableImages[1])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[1], [], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[1], [], true, current, target, false, {}, {}, {})
|
||||
expect(steps).to.eventually.have.deep.members([
|
||||
{
|
||||
action: 'start'
|
||||
@ -347,7 +349,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[3])
|
||||
@normaliseTarget(targetState[4], availableImages[1])
|
||||
(current, target) =>
|
||||
steps = @applications._inferNextSteps(false, availableImages[1], [], true, current, target, false, {})
|
||||
steps = @applications._inferNextSteps(false, availableImages[1], [], true, current, target, false, {}, {})
|
||||
expect(steps).to.eventually.have.deep.members([
|
||||
{
|
||||
action: 'kill'
|
||||
@ -406,7 +408,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[6]),
|
||||
@normaliseTarget(targetState[0], availableImages[0])
|
||||
(current, target) =>
|
||||
@applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {}).then (steps) ->
|
||||
@applications._inferNextSteps(false, availableImages[0], [], true, current, target, false, {}, {}).then (steps) ->
|
||||
expect(
|
||||
_.every(steps, (s) -> s.action != 'removeVolume'),
|
||||
'Volumes from current app should not be removed'
|
||||
@ -418,7 +420,7 @@ describe 'ApplicationManager', ->
|
||||
@normaliseCurrent(currentState[5])
|
||||
@normaliseTarget(targetState[6], [])
|
||||
(current, target) =>
|
||||
@applications._inferNextSteps(false, [], [], true, current, target, false, {}).then (steps) ->
|
||||
@applications._inferNextSteps(false, [], [], true, current, target, false, {}, {}).then (steps) ->
|
||||
expect(steps).to.have.length(1)
|
||||
expect(steps[0]).to.have.property('action').that.equals('removeVolume')
|
||||
expect(steps[0].current).to.have.property('appId').that.equals(12)
|
||||
|
0
test/21-device-api.ts
Normal file
0
test/21-device-api.ts
Normal file
13
test/data/docker-states/network-mode-service/compose.json
Normal file
13
test/data/docker-states/network-mode-service/compose.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"imageId": 431889,
|
||||
"serviceName": "main",
|
||||
"image": "sha256:f9e0fa6e3e68caedbcbb4ef35d5a8dce2a8d33e39cc94115d567800f25d826f4",
|
||||
"running": true,
|
||||
"appId": 1011165,
|
||||
"releaseId": 572579,
|
||||
"serviceId": 43697,
|
||||
"commit": "b14730d691467ab0f448a308af6bf839",
|
||||
"imageName": "registry2.resin.io/v2/8ddbe4a22e881f06def0f31400bfb6de@sha256:09b0db9e71cead5f91107fc9254b1af7088444cc6da55afa2da595940f72a34a",
|
||||
"tty": true,
|
||||
"network_mode": "service: test"
|
||||
}
|
128
test/data/docker-states/network-mode-service/imageInfo.json
Normal file
128
test/data/docker-states/network-mode-service/imageInfo.json
Normal file
@ -0,0 +1,128 @@
|
||||
{
|
||||
"serviceName": "main",
|
||||
"imageInfo": {
|
||||
"Id": "sha256:f9e0fa6e3e68caedbcbb4ef35d5a8dce2a8d33e39cc94115d567800f25d826f4",
|
||||
"RepoTags": [],
|
||||
"RepoDigests": [
|
||||
"registry2.resin.io/v2/8ddbe4a22e881f06def0f31400bfb6de@sha256:09b0db9e71cead5f91107fc9254b1af7088444cc6da55afa2da595940f72a34a"
|
||||
],
|
||||
"Parent": "",
|
||||
"Comment": "",
|
||||
"Created": "2018-08-15T12:43:06.43392045Z",
|
||||
"Container": "b6cc9227f272b905512a58926b6d515b38de34b604386031aa3c21e94d9dbb4a",
|
||||
"ContainerConfig": {
|
||||
"Hostname": "f15babe8256c",
|
||||
"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",
|
||||
"UDEV=on"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"#(nop) ",
|
||||
"CMD [\"/bin/sh\" \"-c\" \"./call-supervisor.sh\"]"
|
||||
],
|
||||
"ArgsEscaped": true,
|
||||
"Image": "sha256:828d725f5e6d09ee9abc214f6c11fadf69192ba4871b050984cc9c4cec37b208",
|
||||
"Volumes": null,
|
||||
"WorkingDir": "/usr/src/app",
|
||||
"Entrypoint": [
|
||||
"/usr/bin/entry.sh"
|
||||
],
|
||||
"OnBuild": [],
|
||||
"Labels": {
|
||||
"io.resin.architecture": "armv7hf",
|
||||
"io.resin.device-type": "raspberry-pi2",
|
||||
"io.resin.qemu.version": "2.9.0.resin1-arm"
|
||||
}
|
||||
},
|
||||
"DockerVersion": "17.05.0-ce",
|
||||
"Author": "",
|
||||
"Config": {
|
||||
"Hostname": "f15babe8256c",
|
||||
"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",
|
||||
"UDEV=on"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"./call-supervisor.sh"
|
||||
],
|
||||
"ArgsEscaped": true,
|
||||
"Image": "sha256:828d725f5e6d09ee9abc214f6c11fadf69192ba4871b050984cc9c4cec37b208",
|
||||
"Volumes": null,
|
||||
"WorkingDir": "/usr/src/app",
|
||||
"Entrypoint": [
|
||||
"/usr/bin/entry.sh"
|
||||
],
|
||||
"OnBuild": [],
|
||||
"Labels": {
|
||||
"io.resin.architecture": "armv7hf",
|
||||
"io.resin.device-type": "raspberry-pi2",
|
||||
"io.resin.qemu.version": "2.9.0.resin1-arm"
|
||||
}
|
||||
},
|
||||
"Architecture": "arm64",
|
||||
"Os": "linux",
|
||||
"Size": 38692178,
|
||||
"VirtualSize": 38692178,
|
||||
"GraphDriver": {
|
||||
"Data": null,
|
||||
"Name": "aufs"
|
||||
},
|
||||
"RootFS": {
|
||||
"Type": "layers",
|
||||
"Layers": [
|
||||
"sha256:7c30ac6ce381873d5388b7d23b346af7d1e5f6af000a84b97e6203ed9e6dcab2",
|
||||
"sha256:450b73019ae79e6a99774fcd37c18769f95065c8b271be936dfb3f93afadc4a8",
|
||||
"sha256:54742c4169d9ff56328f60aea070a6c67f507d4b82389f3432a2c5d92742223c",
|
||||
"sha256:1f609e3b26a8772335a4658ee1980e9b34019d55ac8ee5dcb281f1d4cd5e8e9c",
|
||||
"sha256:c062d099c615146d6dc095254c11babbe120edf06d66419aeef955b88d8543ce",
|
||||
"sha256:2b57e2af57a24bcbafc5bfa04d928ab11695232df7942c294a7c1ca115ba42ca",
|
||||
"sha256:6eb88b69d374abd577336ddc8ab01b25b970020537bb6605676496dcb041b462",
|
||||
"sha256:e410a938934a7ad4f44334cceca97084df7405a5654eefc30cede0aa5bbe8394",
|
||||
"sha256:201b3de34ff5e12e1ada0331d0ce4d0b041059ff9350cb26d8ee15c7be50fe57",
|
||||
"sha256:115ca022a36d9de6fb7a4ba3917545711a0c20564dacf3b189567f68e381e73e",
|
||||
"sha256:4fdd323f81af620a5f19a544a1caa21093f2567d83671ebf24fbde77cefde67c",
|
||||
"sha256:e9a758756b9b5537fe624f87d02f16dd7a523c27a1688de9820f5d2157e5d37d",
|
||||
"sha256:2d082f247d32fd789dbd46fc50054e16810c79a64fa9ea47e0e4845226a0e011",
|
||||
"sha256:6ab67aaf666bfb7001ab93deffe785f24775f4e0da3d6d421ad6096ba869fd0d"
|
||||
]
|
||||
},
|
||||
"Metadata": {
|
||||
"LastTagTime": "0001-01-01T00:00:00Z"
|
||||
}
|
||||
},
|
||||
"appName": "supervisortest",
|
||||
"supervisorApiHost": "172.17.0.1",
|
||||
"hostPathExists": {
|
||||
"firmware": true,
|
||||
"modules": true
|
||||
},
|
||||
"hostnameOnHost": "7dadabd",
|
||||
"uuid": "7dadabd4edec3067948d5952c2f2f26f",
|
||||
"listenPort": "48484",
|
||||
"name": "Office",
|
||||
"apiSecret": "d4bf8369519c32adaa5dd1f84367aa817403f2a3ce976be9c9bacd4d344fdd",
|
||||
"deviceApiKey": "ff89e1d8db58a7ca52a435f2adea319a",
|
||||
"version": "7.16.6",
|
||||
"deviceType": "raspberrypi3",
|
||||
"osVersion": "Resin OS 2.13.6+rev1"
|
||||
}
|
246
test/data/docker-states/network-mode-service/inspect.json
Normal file
246
test/data/docker-states/network-mode-service/inspect.json
Normal file
@ -0,0 +1,246 @@
|
||||
{
|
||||
"Id": "5bff0c9e6ef8ae4fdc02b7ba05050078c8599775e8773ce039982e987107a4a4",
|
||||
"Created": "2018-08-16T13:00:47.100056946Z",
|
||||
"Path": "/usr/bin/entry.sh",
|
||||
"Args": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"./call-supervisor.sh"
|
||||
],
|
||||
"State": {
|
||||
"Status": "restarting",
|
||||
"Running": true,
|
||||
"Paused": false,
|
||||
"Restarting": true,
|
||||
"OOMKilled": false,
|
||||
"Dead": false,
|
||||
"Pid": 0,
|
||||
"ExitCode": 0,
|
||||
"Error": "",
|
||||
"StartedAt": "2018-08-16T13:22:17.991455639Z",
|
||||
"FinishedAt": "2018-08-16T13:22:18.845432218Z"
|
||||
},
|
||||
"Image": "sha256:f9e0fa6e3e68caedbcbb4ef35d5a8dce2a8d33e39cc94115d567800f25d826f4",
|
||||
"ResolvConfPath": "/var/lib/docker/containers/5bff0c9e6ef8ae4fdc02b7ba05050078c8599775e8773ce039982e987107a4a4/resolv.conf",
|
||||
"HostnamePath": "/var/lib/docker/containers/5bff0c9e6ef8ae4fdc02b7ba05050078c8599775e8773ce039982e987107a4a4/hostname",
|
||||
"HostsPath": "/var/lib/docker/containers/5bff0c9e6ef8ae4fdc02b7ba05050078c8599775e8773ce039982e987107a4a4/hosts",
|
||||
"LogPath": "",
|
||||
"Name": "/main_431889_572579",
|
||||
"RestartCount": 29,
|
||||
"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": "container:abcdef",
|
||||
"PortBindings": {},
|
||||
"RestartPolicy": {
|
||||
"Name": "always",
|
||||
"MaximumRetryCount": 0
|
||||
},
|
||||
"AutoRemove": false,
|
||||
"VolumeDriver": "",
|
||||
"VolumesFrom": null,
|
||||
"CapAdd": [],
|
||||
"CapDrop": [],
|
||||
"Dns": [],
|
||||
"DnsOptions": null,
|
||||
"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
|
||||
},
|
||||
"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": "5bff0c9e6ef8",
|
||||
"Domainname": "",
|
||||
"User": "",
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"AttachStderr": false,
|
||||
"Tty": true,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Env": [
|
||||
"RESIN_APP_ID=1011165",
|
||||
"RESIN_APP_NAME=supervisortest",
|
||||
"RESIN_SERVICE_NAME=main",
|
||||
"RESIN_DEVICE_UUID=7dadabd4edec3067948d5952c2f2f26f",
|
||||
"RESIN_DEVICE_TYPE=raspberrypi3",
|
||||
"RESIN_HOST_OS_VERSION=Resin OS 2.13.6+rev1",
|
||||
"RESIN_SUPERVISOR_VERSION=7.16.6",
|
||||
"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_NAME=supervisortest",
|
||||
"BALENA_SERVICE_NAME=main",
|
||||
"BALENA_DEVICE_UUID=7dadabd4edec3067948d5952c2f2f26f",
|
||||
"BALENA_DEVICE_TYPE=raspberrypi3",
|
||||
"BALENA_HOST_OS_VERSION=Resin OS 2.13.6+rev1",
|
||||
"BALENA_SUPERVISOR_VERSION=7.16.6",
|
||||
"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",
|
||||
"UDEV=on"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"./call-supervisor.sh"
|
||||
],
|
||||
"Image": "sha256:f9e0fa6e3e68caedbcbb4ef35d5a8dce2a8d33e39cc94115d567800f25d826f4",
|
||||
"Volumes": null,
|
||||
"WorkingDir": "/usr/src/app",
|
||||
"Entrypoint": [
|
||||
"/usr/bin/entry.sh"
|
||||
],
|
||||
"OnBuild": null,
|
||||
"Labels": {
|
||||
"io.resin.app-id": "1011165",
|
||||
"io.resin.architecture": "armv7hf",
|
||||
"io.resin.device-type": "raspberry-pi2",
|
||||
"io.resin.qemu.version": "2.9.0.resin1-arm",
|
||||
"io.resin.service-id": "43697",
|
||||
"io.resin.service-name": "main",
|
||||
"io.resin.supervised": "true"
|
||||
}
|
||||
},
|
||||
"NetworkSettings": {
|
||||
"Bridge": "",
|
||||
"SandboxID": "9124030ea083331f46529d0ffb1549f780b5ca913e912d438726e31fc5c716da",
|
||||
"HairpinMode": false,
|
||||
"LinkLocalIPv6Address": "",
|
||||
"LinkLocalIPv6PrefixLen": 0,
|
||||
"Ports": {},
|
||||
"SandboxKey": "/var/run/balena/netns/9124030ea083",
|
||||
"SecondaryIPAddresses": null,
|
||||
"SecondaryIPv6Addresses": null,
|
||||
"EndpointID": "",
|
||||
"Gateway": "",
|
||||
"GlobalIPv6Address": "",
|
||||
"GlobalIPv6PrefixLen": 0,
|
||||
"IPAddress": "",
|
||||
"IPPrefixLen": 0,
|
||||
"IPv6Gateway": "",
|
||||
"MacAddress": "",
|
||||
"Networks": {
|
||||
"1011165_default": {
|
||||
"IPAMConfig": null,
|
||||
"Links": null,
|
||||
"Aliases": [
|
||||
"main",
|
||||
"5bff0c9e6ef8"
|
||||
],
|
||||
"NetworkID": "4afe52d663d8de16ea7cbd3c3faaff0705109d2f246e4f18cbed3b2789ce0a7a",
|
||||
"EndpointID": "",
|
||||
"Gateway": "",
|
||||
"IPAddress": "",
|
||||
"IPPrefixLen": 0,
|
||||
"IPv6Gateway": "",
|
||||
"GlobalIPv6Address": "",
|
||||
"GlobalIPv6PrefixLen": 0,
|
||||
"MacAddress": "",
|
||||
"DriverOpts": null
|
||||
},
|
||||
"supervisor0": {
|
||||
"IPAMConfig": null,
|
||||
"Links": null,
|
||||
"Aliases": [
|
||||
"5bff0c9e6ef8"
|
||||
],
|
||||
"NetworkID": "2ad3a9f0a52d912fff9990837167cd7a3d9f1133e73a40cbed6438be81a96126",
|
||||
"EndpointID": "",
|
||||
"Gateway": "",
|
||||
"IPAddress": "",
|
||||
"IPPrefixLen": 0,
|
||||
"IPv6Gateway": "",
|
||||
"GlobalIPv6Address": "",
|
||||
"GlobalIPv6PrefixLen": 0,
|
||||
"MacAddress": "",
|
||||
"DriverOpts": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user