Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create mechanism to read files from host / #2082

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
68 changes: 68 additions & 0 deletions src/lib/host-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { spawn } from 'child_process';
import * as path from 'path';
import * as constants from './constants';
import { exec, exists } from './fs-utils';
import { PassThrough } from 'stream';
import { docker } from './docker-utils';

// Returns an absolute path starting from the hostOS root partition
// This path is accessible from within the Supervisor container
Expand All @@ -21,6 +23,72 @@ class CodedError extends Error {
}
}

/**
* This function is used to read a file from the hostOS
*
* This function launches a docker container with a mount into
* the hostOS root partition, and then reads the file from there.
* This is an expensive operation and should be used ideally never.
*
* TODO: remove this when the OS has a dbus API for reading the OS version
* and the VPN status
*/
export async function readFromRoot(
fileName: string,
encoding: 'utf8' | 'utf-8',
): Promise<string>;
export async function readFromRoot(fileName: string): Promise<Buffer>;
export async function readFromRoot(
fileName: string,
encoding?: 'utf8' | 'utf-8',
) {
const stdout = new PassThrough();

return new Promise(async (resolve) => {
const chunks: Buffer[] = [];
stdout.on('data', (chunk) => {
chunks.push(Buffer.from(chunk));
});

stdout.on('end', () => {
const buf = Buffer.concat(chunks);
if (encoding) {
resolve(buf.toString(encoding));
} else {
resolve(buf);
}
});

const [output] = await docker.run(
// TODO: use the supervisor image for this. We need first to get
// the image from the current container
'alpine:latest',
['cat', fileName],
stdout,
{
HostConfig: {
Mounts: [
{
type: 'bind',
source: fileName,
target: fileName,
readonly: true,
},
],
AutoRemove: true,
},
},
);

if (output.StatusCode !== 0) {
throw new CodedError(
`Failed to read ${fileName} from the hostOS`,
'ENOENT',
);
}
});
}

// Receives an absolute path for a file (assumed to be under the boot partition, e.g. `/mnt/root/mnt/boot/config.txt`)
// and reads from the given location. This function uses fatrw to safely read from a FAT filesystem
// https://github.com/balena-os/fatrw
Expand Down
29 changes: 4 additions & 25 deletions src/network.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import { promises as fs, watch } from 'fs';
import { promises as fs } from 'fs';
import * as networkCheck from 'network-checker';
import * as os from 'os';
import * as url from 'url';

import * as constants from './lib/constants';
import { EEXIST } from './lib/errors';
import { checkFalsey } from './lib/validation';
import { readFromRoot } from './lib/host-utils';

import blink = require('./lib/blink');

Expand All @@ -18,17 +18,12 @@ const networkPattern = {
pause: 1000,
};

let isConnectivityCheckPaused = false;
let isConnectivityCheckEnabled = true;

function checkHost(
opts: networkCheck.ConnectOptions,
): boolean | PromiseLike<boolean> {
return (
!isConnectivityCheckEnabled ||
isConnectivityCheckPaused ||
networkCheck.checkHost(opts)
);
return !isConnectivityCheckEnabled || networkCheck.checkHost(opts);
}

function customMonitor(
Expand All @@ -45,18 +40,14 @@ export function enableCheck(enable: boolean) {
export async function isVPNActive(): Promise<boolean> {
let active: boolean = true;
try {
await fs.lstat(`${constants.vpnStatusPath}/active`);
await readFromRoot(`${constants.vpnStatusPath}/active`);
} catch {
active = false;
}
log.info(`VPN connection is ${active ? 'active' : 'not active'}.`);
return active;
}

async function vpnStatusInotifyCallback(): Promise<void> {
isConnectivityCheckPaused = await isVPNActive();
}

export const startConnectivityCheck = _.once(
async (
apiEndpoint: string,
Expand All @@ -69,18 +60,6 @@ export const startConnectivityCheck = _.once(
return;
}

await Bluebird.resolve(fs.mkdir(constants.vpnStatusPath))
.catch(EEXIST, () => {
log.debug('VPN status path exists.');
})
.then(() => {
watch(constants.vpnStatusPath, vpnStatusInotifyCallback);
});

if (enable) {
vpnStatusInotifyCallback();
}

const parsedUrl = url.parse(apiEndpoint);
const port = parseInt(parsedUrl.port!, 10);

Expand Down