mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-05-30 22:34:19 +00:00
Merge pull request #1989 from balena-os/disable-events
Disable event tracking
This commit is contained in:
commit
437a24e2f1
@ -68,12 +68,10 @@ RUN apk add --no-cache \
|
||||
|
||||
ARG ARCH
|
||||
ARG VERSION=master
|
||||
ARG DEFAULT_MIXPANEL_TOKEN=bananasbananas
|
||||
ENV CONFIG_MOUNT_POINT=/boot/config.json \
|
||||
LED_FILE=/dev/null \
|
||||
SUPERVISOR_IMAGE=balena/$ARCH-supervisor \
|
||||
VERSION=$VERSION \
|
||||
DEFAULT_MIXPANEL_TOKEN=$DEFAULT_MIXPANEL_TOKEN
|
||||
VERSION=$VERSION
|
||||
|
||||
###############################################################
|
||||
# Use the base image to run integration tests and for livepush
|
||||
|
129
package-lock.json
generated
129
package-lock.json
generated
@ -78,7 +78,6 @@
|
||||
"livepush": "^3.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"memoizee": "^0.4.14",
|
||||
"mixpanel": "^0.10.3",
|
||||
"mocha": "^8.3.2",
|
||||
"mocha-pod": "^0.6.0",
|
||||
"mock-fs": "^4.14.0",
|
||||
@ -2077,18 +2076,6 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es6-promisify": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agentkeepalive": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz",
|
||||
@ -5381,21 +5368,6 @@
|
||||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es6-promisify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
|
||||
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es6-promise": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-symbol": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
|
||||
@ -7498,35 +7470,6 @@
|
||||
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz",
|
||||
"integrity": "sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"agent-base": "^4.3.0",
|
||||
"debug": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent/node_modules/debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz",
|
||||
@ -9870,18 +9813,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mixpanel": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/mixpanel/-/mixpanel-0.10.3.tgz",
|
||||
"integrity": "sha512-wIYr5o+1XSzJ80o3QED35K/yfPAKi5FigZXTSfcs4vltfeKbilIjNgwxdno7LrqzhjoSjmIyDWkI7D3lr7TwDw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"https-proxy-agent": "3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
@ -18117,15 +18048,6 @@
|
||||
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
|
||||
"dev": true
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es6-promisify": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"agentkeepalive": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz",
|
||||
@ -20844,21 +20766,6 @@
|
||||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
|
||||
"dev": true
|
||||
},
|
||||
"es6-promisify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
|
||||
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es6-promise": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"es6-symbol": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
|
||||
@ -22510,33 +22417,6 @@
|
||||
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
|
||||
"dev": true
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.0.tgz",
|
||||
"integrity": "sha512-y4jAxNEihqvBI5F3SaO2rtsjIOnnNA8sEbuiP+UhJZJHeM2NRm6c09ax2tgqme+SgUUvjao2fJXF4h3D6Cb2HQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"agent-base": "^4.3.0",
|
||||
"debug": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"human-signals": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz",
|
||||
@ -24376,15 +24256,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mixpanel": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/mixpanel/-/mixpanel-0.10.3.tgz",
|
||||
"integrity": "sha512-wIYr5o+1XSzJ80o3QED35K/yfPAKi5FigZXTSfcs4vltfeKbilIjNgwxdno7LrqzhjoSjmIyDWkI7D3lr7TwDw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"https-proxy-agent": "3.0.0"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
|
@ -103,7 +103,6 @@
|
||||
"livepush": "^3.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"memoizee": "^0.4.14",
|
||||
"mixpanel": "^0.10.3",
|
||||
"mocha": "^8.3.2",
|
||||
"mocha-pod": "^0.6.0",
|
||||
"mock-fs": "^4.14.0",
|
||||
|
@ -546,7 +546,6 @@ export let balenaApi: PinejsClientRequest | null = null;
|
||||
|
||||
export const initialized = _.once(async () => {
|
||||
await config.initialized();
|
||||
await eventTracker.initialized();
|
||||
await deviceState.initialized();
|
||||
|
||||
const { unmanaged, apiEndpoint, currentApiKey } = await config.getMany([
|
||||
|
@ -2,7 +2,6 @@ import * as Bluebird from 'bluebird';
|
||||
import * as _ from 'lodash';
|
||||
import * as memoizee from 'memoizee';
|
||||
import { promises as fs } from 'fs';
|
||||
import { URL } from 'url';
|
||||
|
||||
import supervisorVersion = require('../lib/supervisor-version');
|
||||
|
||||
@ -114,15 +113,6 @@ export const fnSchema = {
|
||||
};
|
||||
});
|
||||
},
|
||||
mixpanelHost: () => {
|
||||
return config.get('apiEndpoint').then((apiEndpoint) => {
|
||||
if (!apiEndpoint) {
|
||||
return null;
|
||||
}
|
||||
const url = new URL(apiEndpoint);
|
||||
return { host: url.host, path: '/mixpanel' };
|
||||
});
|
||||
},
|
||||
extendedEnvOptions: () => {
|
||||
return config.getMany([
|
||||
'uuid',
|
||||
|
@ -1,7 +1,5 @@
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import * as constants from '../lib/constants';
|
||||
|
||||
import {
|
||||
NullOrUndefined,
|
||||
PermissiveBoolean,
|
||||
@ -66,10 +64,6 @@ export const schemaTypes = {
|
||||
type: PermissiveBoolean,
|
||||
default: true,
|
||||
},
|
||||
mixpanelToken: {
|
||||
type: t.string,
|
||||
default: constants.defaultMixpanelToken,
|
||||
},
|
||||
bootstrapRetryDelay: {
|
||||
type: PermissiveNumber,
|
||||
default: 30000,
|
||||
@ -226,10 +220,6 @@ export const schemaTypes = {
|
||||
}),
|
||||
default: t.never,
|
||||
},
|
||||
mixpanelHost: {
|
||||
type: t.union([t.null, t.interface({ host: t.string, path: t.string })]),
|
||||
default: t.never,
|
||||
},
|
||||
extendedEnvOptions: {
|
||||
type: t.interface({
|
||||
uuid: t.union([t.string, NullOrUndefined]),
|
||||
|
@ -54,11 +54,6 @@ export const schema = {
|
||||
mutable: true,
|
||||
removeIfNull: false,
|
||||
},
|
||||
mixpanelToken: {
|
||||
source: 'config.json',
|
||||
mutable: false,
|
||||
removeIfNull: false,
|
||||
},
|
||||
bootstrapRetryDelay: {
|
||||
source: 'config.json',
|
||||
mutable: false,
|
||||
|
@ -1,18 +1,10 @@
|
||||
import mask = require('json-mask');
|
||||
import * as _ from 'lodash';
|
||||
import * as memoizee from 'memoizee';
|
||||
import * as mixpanel from 'mixpanel';
|
||||
|
||||
import * as config from './config';
|
||||
import log from './lib/supervisor-console';
|
||||
import supervisorVersion = require('./lib/supervisor-version');
|
||||
|
||||
export type EventTrackProperties = Dictionary<any>;
|
||||
|
||||
// The minimum amount of time to wait between sending
|
||||
// events of the same type
|
||||
const eventDebounceTime = 60000;
|
||||
|
||||
const mixpanelMask = [
|
||||
'appId',
|
||||
'delay',
|
||||
@ -24,39 +16,10 @@ const mixpanelMask = [
|
||||
'stateDiff/local(os_version,supervisor_version,ip_address,apps/*/services)',
|
||||
].join(',');
|
||||
|
||||
let defaultProperties: EventTrackProperties;
|
||||
// We must export this for the tests, but we make no references
|
||||
// to it within the rest of the supervisor codebase
|
||||
export let client: mixpanel.Mixpanel | null = null;
|
||||
|
||||
export const initialized = _.once(async () => {
|
||||
await config.initialized();
|
||||
|
||||
const { unmanaged, mixpanelHost, mixpanelToken, uuid } = await config.getMany(
|
||||
['unmanaged', 'mixpanelHost', 'mixpanelToken', 'uuid'],
|
||||
);
|
||||
|
||||
defaultProperties = {
|
||||
distinct_id: uuid,
|
||||
uuid,
|
||||
supervisorVersion,
|
||||
};
|
||||
|
||||
if (unmanaged || mixpanelHost == null || mixpanelToken == null) {
|
||||
return;
|
||||
}
|
||||
client = mixpanel.init(mixpanelToken, {
|
||||
host: mixpanelHost.host,
|
||||
path: mixpanelHost.path,
|
||||
});
|
||||
});
|
||||
|
||||
export async function track(
|
||||
event: string,
|
||||
properties: EventTrackProperties | Error = {},
|
||||
) {
|
||||
await initialized();
|
||||
|
||||
if (properties instanceof Error) {
|
||||
properties = { error: properties };
|
||||
}
|
||||
@ -73,30 +36,4 @@ export async function track(
|
||||
// Don't send potentially sensitive information, by using a whitelist
|
||||
properties = mask(properties, mixpanelMask);
|
||||
log.event('Event:', event, JSON.stringify(properties));
|
||||
if (client == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
properties = assignDefaultProperties(properties);
|
||||
throttleddLogger(event)(properties);
|
||||
}
|
||||
|
||||
const throttleddLogger = memoizee(
|
||||
(event: string) => {
|
||||
// Call this function at maximum once every minute
|
||||
return _.throttle(
|
||||
(properties: EventTrackProperties | Error) => {
|
||||
client?.track(event, properties);
|
||||
},
|
||||
eventDebounceTime,
|
||||
{ leading: true },
|
||||
);
|
||||
},
|
||||
{ primitive: true },
|
||||
);
|
||||
|
||||
function assignDefaultProperties(
|
||||
properties: EventTrackProperties,
|
||||
): EventTrackProperties {
|
||||
return _.merge({}, properties, defaultProperties);
|
||||
}
|
||||
|
@ -156,7 +156,6 @@ export const provision = async (
|
||||
opts: KeyExchangeOpts,
|
||||
) => {
|
||||
await config.initialized();
|
||||
await eventTracker.initialized();
|
||||
|
||||
let device: Device | null = null;
|
||||
|
||||
|
@ -41,7 +41,6 @@ const constants = {
|
||||
configJsonPathOnHost: checkString(process.env.CONFIG_JSON_PATH),
|
||||
proxyvisorHookReceiver: 'http://0.0.0.0:1337',
|
||||
configJsonNonAtomicPath: '/boot/config.json',
|
||||
defaultMixpanelToken: process.env.DEFAULT_MIXPANEL_TOKEN,
|
||||
supervisorNetworkInterface,
|
||||
allowedInterfaces: [
|
||||
'resin-vpn',
|
||||
|
@ -2,7 +2,6 @@ import * as apiBinder from './api-binder';
|
||||
import * as db from './db';
|
||||
import * as config from './config';
|
||||
import * as deviceState from './device-state';
|
||||
import * as eventTracker from './event-tracker';
|
||||
import { intialiseContractRequirements } from './lib/contracts';
|
||||
import { normaliseLegacyDatabase } from './lib/legacy';
|
||||
import * as osRelease from './lib/os-release';
|
||||
@ -21,8 +20,6 @@ const startupConfigFields: config.ConfigKey[] = [
|
||||
'apiTimeout',
|
||||
'unmanaged',
|
||||
'deviceApiKey',
|
||||
'mixpanelToken',
|
||||
'mixpanelHost',
|
||||
'loggingEnabled',
|
||||
'localMode',
|
||||
'legacyAppsPresent',
|
||||
@ -36,7 +33,6 @@ export class Supervisor {
|
||||
|
||||
await db.initialized();
|
||||
await config.initialized();
|
||||
await eventTracker.initialized();
|
||||
await avahi.initialized();
|
||||
log.debug('Starting logging infrastructure');
|
||||
await logger.initialized();
|
||||
|
@ -5,8 +5,9 @@ import { Builder } from 'resin-docker-build';
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import * as Path from 'path';
|
||||
import { Duplex, Readable, PassThrough, Stream } from 'stream';
|
||||
import { Readable } from 'stream';
|
||||
import * as tar from 'tar-stream';
|
||||
import * as readline from 'readline';
|
||||
|
||||
import { exec } from '../src/lib/fs-utils';
|
||||
|
||||
@ -52,7 +53,7 @@ export async function getDeviceArch(docker: Docker): Promise<string> {
|
||||
}
|
||||
|
||||
return arch.trim();
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
throw new Error(
|
||||
`Unable to get device architecture: ${e.message}.\nTry specifying the architecture with -a.`,
|
||||
);
|
||||
@ -68,31 +69,61 @@ export async function getCacheFrom(docker: Docker): Promise<string[]> {
|
||||
}
|
||||
}
|
||||
|
||||
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L539-L547
|
||||
function extractDockerArrowMessage(outputLine: string): string | undefined {
|
||||
const arrowTest = /^.*\s*-+>\s*(.+)/i;
|
||||
const match = arrowTest.exec(outputLine);
|
||||
if (match != null) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
export async function performBuild(
|
||||
docker: Docker,
|
||||
dockerfile: Dockerfile,
|
||||
dockerOpts: { [key: string]: any },
|
||||
): Promise<string> {
|
||||
): Promise<string[]> {
|
||||
const builder = Builder.fromDockerode(docker);
|
||||
|
||||
// tar the directory, but replace the dockerfile with the
|
||||
// livepush generated one
|
||||
const tarStream = await tarDirectory(Path.join(__dirname, '..'), dockerfile);
|
||||
const bufStream = new PassThrough();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks = [] as Buffer[];
|
||||
bufStream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
||||
// Store the stage ids for caching
|
||||
const ids = [] as string[];
|
||||
builder.createBuildStream(dockerOpts, {
|
||||
buildSuccess: () => {
|
||||
// Return the build logs
|
||||
resolve(Buffer.concat(chunks).toString('utf8'));
|
||||
// Return the image ids
|
||||
resolve(ids);
|
||||
},
|
||||
buildFailure: reject,
|
||||
buildStream: (stream: Duplex) => {
|
||||
stream.pipe(process.stdout);
|
||||
stream.pipe(bufStream);
|
||||
tarStream.pipe(stream);
|
||||
buildStream: (input: NodeJS.ReadWriteStream) => {
|
||||
// Parse the build output to get stage ids and
|
||||
// for logging
|
||||
let lastArrowMessage: string | undefined;
|
||||
readline.createInterface({ input }).on('line', (line) => {
|
||||
// If this was a FROM line, take the last found
|
||||
// image id and save it as a stage id
|
||||
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L300-L325
|
||||
if (
|
||||
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
|
||||
lastArrowMessage != null
|
||||
) {
|
||||
ids.push(lastArrowMessage);
|
||||
} else {
|
||||
const msg = extractDockerArrowMessage(line);
|
||||
if (msg != null) {
|
||||
lastArrowMessage = msg;
|
||||
}
|
||||
}
|
||||
|
||||
// Log the build line
|
||||
console.info(line);
|
||||
});
|
||||
|
||||
// stream.pipe(bufStream);
|
||||
tarStream.pipe(input);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
37
sync/init.ts
37
sync/init.ts
@ -14,39 +14,6 @@ interface Opts {
|
||||
arch?: string;
|
||||
}
|
||||
|
||||
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L539-L547
|
||||
function extractDockerArrowMessage(outputLine: string): string | undefined {
|
||||
const arrowTest = /^.*\s*-+>\s*(.+)/i;
|
||||
const match = arrowTest.exec(outputLine);
|
||||
if (match != null) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L300-L325
|
||||
function getMultiStateImageIDs(buildLog: string): string[] {
|
||||
const ids = [] as string[];
|
||||
const lines = buildLog.split(/\r?\n/);
|
||||
let lastArrowMessage: string | undefined;
|
||||
for (const line of lines) {
|
||||
// If this was a from line, take the last found
|
||||
// image id and save it
|
||||
if (
|
||||
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
|
||||
lastArrowMessage != null
|
||||
) {
|
||||
ids.push(lastArrowMessage);
|
||||
} else {
|
||||
const msg = extractDockerArrowMessage(line);
|
||||
if (msg != null) {
|
||||
lastArrowMessage = msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
function getPathPrefix(arch: string) {
|
||||
switch (arch) {
|
||||
/**
|
||||
@ -74,7 +41,7 @@ export async function initDevice(opts: Opts) {
|
||||
|
||||
const buildCache = await device.readBuildCache(opts.address);
|
||||
|
||||
const buildLog = await device.performBuild(opts.docker, opts.dockerfile, {
|
||||
const stageImages = await device.performBuild(opts.docker, opts.dockerfile, {
|
||||
buildargs: { ARCH: arch, PREFIX: getPathPrefix(arch) },
|
||||
t: image,
|
||||
labels: { 'io.balena.livepush-image': '1', 'io.balena.architecture': arch },
|
||||
@ -84,8 +51,6 @@ export async function initDevice(opts: Opts) {
|
||||
nocache: opts.nocache,
|
||||
});
|
||||
|
||||
const stageImages = getMultiStateImageIDs(buildLog);
|
||||
|
||||
// Store the list of stage images for the next time the sync
|
||||
// command is called. This will only live until the device is rebooted
|
||||
await device.writeBuildCache(opts.address, stageImages);
|
||||
|
@ -93,12 +93,12 @@ const argv = yargs
|
||||
sigint = () => reject(new Error('User interrupt (Ctrl+C) received'));
|
||||
process.on('SIGINT', sigint);
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
console.error('Error:', e.message);
|
||||
} finally {
|
||||
console.info('Cleaning up. Please wait ...');
|
||||
await cleanup();
|
||||
process.removeListener('SIGINT', sigint);
|
||||
process.exit(1);
|
||||
process.exit(0);
|
||||
}
|
||||
})();
|
||||
|
@ -1,248 +0,0 @@
|
||||
import { SinonStub, stub, spy, SinonSpy } from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import * as mixpanel from 'mixpanel';
|
||||
|
||||
import log from '~/lib/supervisor-console';
|
||||
import supervisorVersion = require('~/lib/supervisor-version');
|
||||
import * as config from '~/src/config';
|
||||
|
||||
describe('EventTracker', () => {
|
||||
let logEventStub: SinonStub;
|
||||
before(() => {
|
||||
logEventStub = stub(log, 'event');
|
||||
|
||||
delete require.cache[require.resolve('~/src/event-tracker')];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
logEventStub.reset();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
logEventStub.restore();
|
||||
});
|
||||
|
||||
describe('Unmanaged', () => {
|
||||
let configStub: SinonStub;
|
||||
let eventTracker: typeof import('~/src/event-tracker');
|
||||
|
||||
before(async () => {
|
||||
configStub = stub(config, 'getMany').returns(
|
||||
Promise.resolve({
|
||||
unmanaged: true,
|
||||
uuid: 'foobar',
|
||||
mixpanelHost: { host: '', path: '' },
|
||||
mixpanelToken: '',
|
||||
}) as any,
|
||||
);
|
||||
|
||||
eventTracker = await import('~/src/event-tracker');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
configStub.restore();
|
||||
|
||||
delete require.cache[require.resolve('~/src/event-tracker')];
|
||||
});
|
||||
|
||||
it('initializes in unmanaged mode', () => {
|
||||
expect(eventTracker.initialized()).to.be.fulfilled.then(() => {
|
||||
expect(eventTracker.client).to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
it('logs events in unmanaged mode, with the correct properties', async () => {
|
||||
await eventTracker.track('Test event', { appId: 'someValue' });
|
||||
expect(logEventStub).to.be.calledWith(
|
||||
'Event:',
|
||||
'Test event',
|
||||
JSON.stringify({ appId: 'someValue' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Init', () => {
|
||||
let eventTracker: typeof import('~/src/event-tracker');
|
||||
let configStub: SinonStub;
|
||||
let mixpanelSpy: SinonSpy;
|
||||
|
||||
before(async () => {
|
||||
configStub = stub(config, 'getMany').returns(
|
||||
Promise.resolve({
|
||||
mixpanelToken: 'someToken',
|
||||
uuid: 'barbaz',
|
||||
mixpanelHost: { host: '', path: '' },
|
||||
unmanaged: false,
|
||||
}) as any,
|
||||
);
|
||||
|
||||
mixpanelSpy = spy(mixpanel, 'init');
|
||||
|
||||
eventTracker = await import('~/src/event-tracker');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
configStub.restore();
|
||||
mixpanelSpy.restore();
|
||||
|
||||
delete require.cache[require.resolve('~/src/event-tracker')];
|
||||
});
|
||||
|
||||
it('initializes a mixpanel client when not in unmanaged mode', () => {
|
||||
expect(eventTracker.initialized()).to.be.fulfilled.then(() => {
|
||||
expect(mixpanel.init).to.have.been.calledWith('someToken');
|
||||
// @ts-expect-error
|
||||
expect(eventTracker.client.token).to.equal('someToken');
|
||||
// @ts-expect-error
|
||||
expect(eventTracker.client.track).to.be.a('function');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Managed', () => {
|
||||
let eventTracker: typeof import('~/src/event-tracker');
|
||||
let configStub: SinonStub;
|
||||
let mixpanelStub: SinonStub;
|
||||
|
||||
before(async () => {
|
||||
configStub = stub(config, 'getMany').returns(
|
||||
Promise.resolve({
|
||||
mixpanelToken: 'someToken',
|
||||
uuid: 'barbaz',
|
||||
mixpanelHost: { host: '', path: '' },
|
||||
unmanaged: false,
|
||||
}) as any,
|
||||
);
|
||||
|
||||
mixpanelStub = stub(mixpanel, 'init').returns({
|
||||
token: 'someToken',
|
||||
track: stub(),
|
||||
} as any);
|
||||
|
||||
eventTracker = await import('~/src/event-tracker');
|
||||
await eventTracker.initialized();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
configStub.restore();
|
||||
mixpanelStub.restore();
|
||||
|
||||
delete require.cache[require.resolve('~/src/event-tracker')];
|
||||
});
|
||||
|
||||
it('calls the mixpanel client track function with the event, properties and uuid as distinct_id', async () => {
|
||||
await eventTracker.track('Test event 2', { appId: 'someOtherValue' });
|
||||
|
||||
expect(logEventStub).to.be.calledWith(
|
||||
'Event:',
|
||||
'Test event 2',
|
||||
JSON.stringify({ appId: 'someOtherValue' }),
|
||||
);
|
||||
// @ts-expect-error
|
||||
expect(eventTracker.client.track).to.be.calledWith('Test event 2', {
|
||||
appId: 'someOtherValue',
|
||||
uuid: 'barbaz',
|
||||
distinct_id: 'barbaz',
|
||||
supervisorVersion,
|
||||
});
|
||||
});
|
||||
|
||||
it('can be passed an Error and it is added to the event properties', async () => {
|
||||
const theError = new Error('something went wrong');
|
||||
await eventTracker.track('Error event', theError);
|
||||
// @ts-expect-error
|
||||
expect(eventTracker.client.track).to.be.calledWith('Error event', {
|
||||
error: {
|
||||
message: theError.message,
|
||||
stack: theError.stack,
|
||||
},
|
||||
uuid: 'barbaz',
|
||||
distinct_id: 'barbaz',
|
||||
supervisorVersion,
|
||||
});
|
||||
});
|
||||
|
||||
it('hides service environment variables, to avoid logging keys or secrets', async () => {
|
||||
const props = {
|
||||
service: {
|
||||
appId: '1',
|
||||
environment: {
|
||||
RESIN_API_KEY: 'foo',
|
||||
RESIN_SUPERVISOR_API_KEY: 'bar',
|
||||
OTHER_VAR: 'hi',
|
||||
},
|
||||
},
|
||||
};
|
||||
await eventTracker.track('Some app event', props);
|
||||
// @ts-expect-error
|
||||
expect(eventTracker.client.track).to.be.calledWith('Some app event', {
|
||||
service: { appId: '1' },
|
||||
uuid: 'barbaz',
|
||||
distinct_id: 'barbaz',
|
||||
supervisorVersion,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle being passed no properties object', () => {
|
||||
expect(eventTracker.track('no-options')).to.be.fulfilled;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rate limiting', () => {
|
||||
let eventTracker: typeof import('~/src/event-tracker');
|
||||
let mixpanelStub: SinonStub;
|
||||
|
||||
before(async () => {
|
||||
mixpanelStub = stub(mixpanel, 'init').returns({
|
||||
track: stub(),
|
||||
} as any);
|
||||
eventTracker = await import('~/src/event-tracker');
|
||||
await eventTracker.initialized();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
mixpanelStub.restore();
|
||||
|
||||
delete require.cache[require.resolve('~/src/event-tracker')];
|
||||
});
|
||||
|
||||
it('should rate limit events of the same type', async () => {
|
||||
// @ts-expect-error resetting a non-stub typed function
|
||||
eventTracker.client?.track.reset();
|
||||
|
||||
await eventTracker.track('test', {});
|
||||
await eventTracker.track('test', {});
|
||||
await eventTracker.track('test', {});
|
||||
await eventTracker.track('test', {});
|
||||
await eventTracker.track('test', {});
|
||||
|
||||
expect(eventTracker.client?.track).to.have.callCount(1);
|
||||
});
|
||||
|
||||
it('should rate limit events of the same type with different arguments', async () => {
|
||||
// @ts-expect-error resetting a non-stub typed function
|
||||
eventTracker.client?.track.reset();
|
||||
|
||||
await eventTracker.track('test2', { a: 1 });
|
||||
await eventTracker.track('test2', { b: 2 });
|
||||
await eventTracker.track('test2', { c: 3 });
|
||||
await eventTracker.track('test2', { d: 4 });
|
||||
await eventTracker.track('test2', { e: 5 });
|
||||
|
||||
expect(eventTracker.client?.track).to.have.callCount(1);
|
||||
});
|
||||
|
||||
it('should not rate limit events of different types', async () => {
|
||||
// @ts-expect-error resetting a non-stub typed function
|
||||
eventTracker.client?.track.reset();
|
||||
|
||||
await eventTracker.track('test3', { a: 1 });
|
||||
await eventTracker.track('test4', { b: 2 });
|
||||
await eventTracker.track('test5', { c: 3 });
|
||||
await eventTracker.track('test6', { d: 4 });
|
||||
await eventTracker.track('test7', { e: 5 });
|
||||
|
||||
expect(eventTracker.client?.track).to.have.callCount(5);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user