mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-18 21:27:51 +00:00
WIP remove request library
Change-type: patch
This commit is contained in:
parent
544f615ca0
commit
a1d691da02
@ -19,7 +19,7 @@ import { getVisuals } from './lazy';
|
||||
import { promisify } from 'util';
|
||||
import type * as Dockerode from 'dockerode';
|
||||
import type Logger = require('./logger');
|
||||
import type { Request } from 'request';
|
||||
import type got from 'got';
|
||||
|
||||
const getBuilderPushEndpoint = function (
|
||||
baseUrl: string,
|
||||
@ -75,7 +75,10 @@ const showPushProgress = function (message: string) {
|
||||
return progressBar;
|
||||
};
|
||||
|
||||
const uploadToPromise = (uploadRequest: Request, logger: Logger) =>
|
||||
const uploadToPromise = (
|
||||
uploadRequest: ReturnType<typeof got.stream.post>,
|
||||
logger: Logger,
|
||||
) =>
|
||||
new Promise<{ buildId: number }>(function (resolve, reject) {
|
||||
uploadRequest.on('error', reject).on('data', function handleMessage(data) {
|
||||
let obj;
|
||||
@ -109,7 +112,7 @@ const uploadToPromise = (uploadRequest: Request, logger: Logger) =>
|
||||
/**
|
||||
* @returns {Promise<{ buildId: number }>}
|
||||
*/
|
||||
const uploadImage = function (
|
||||
const uploadImage = async function (
|
||||
imageStream: NodeJS.ReadableStream & { length: number },
|
||||
token: string,
|
||||
username: string,
|
||||
@ -117,7 +120,7 @@ const uploadImage = function (
|
||||
appName: string,
|
||||
logger: Logger,
|
||||
): Promise<{ buildId: number }> {
|
||||
const request = require('request') as typeof import('request');
|
||||
const { default: got } = require('got') as typeof import('got');
|
||||
const progressStream =
|
||||
require('progress-stream') as typeof import('progress-stream');
|
||||
const zlib = require('zlib') as typeof import('zlib');
|
||||
@ -141,25 +144,22 @@ const uploadImage = function (
|
||||
),
|
||||
);
|
||||
|
||||
const uploadRequest = request.post({
|
||||
url: getBuilderPushEndpoint(url, username, appName),
|
||||
headers: {
|
||||
'Content-Encoding': 'gzip',
|
||||
const uploadRequest = got.stream.post(
|
||||
getBuilderPushEndpoint(url, username, appName),
|
||||
{
|
||||
headers: {
|
||||
'Content-Encoding': 'gzip',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: streamWithProgress.pipe(zlib.createGzip({ level: 6 })),
|
||||
throwHttpErrors: false,
|
||||
},
|
||||
auth: {
|
||||
bearer: token,
|
||||
},
|
||||
body: streamWithProgress.pipe(
|
||||
zlib.createGzip({
|
||||
level: 6,
|
||||
}),
|
||||
),
|
||||
});
|
||||
);
|
||||
|
||||
return uploadToPromise(uploadRequest, logger);
|
||||
};
|
||||
|
||||
const uploadLogs = function (
|
||||
const uploadLogs = async function (
|
||||
logs: string,
|
||||
token: string,
|
||||
url: string,
|
||||
@ -167,15 +167,18 @@ const uploadLogs = function (
|
||||
username: string,
|
||||
appName: string,
|
||||
) {
|
||||
const request = require('request') as typeof import('request');
|
||||
return request.post({
|
||||
json: true,
|
||||
url: getBuilderLogPushEndpoint(url, buildId, username, appName),
|
||||
auth: {
|
||||
bearer: token,
|
||||
const { default: got } = await import('got');
|
||||
return await got.post(
|
||||
getBuilderLogPushEndpoint(url, buildId, username, appName),
|
||||
{
|
||||
body: Buffer.from(logs),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
responseType: 'json',
|
||||
throwHttpErrors: false,
|
||||
},
|
||||
body: Buffer.from(logs),
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -15,12 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import * as _ from 'lodash';
|
||||
import * as request from 'request';
|
||||
import type * as Stream from 'stream';
|
||||
|
||||
import { retry } from '../helpers';
|
||||
import Logger = require('../logger');
|
||||
import * as ApiErrors from './errors';
|
||||
import { getBalenaSdk } from '../lazy';
|
||||
import type { BalenaSDK } from 'balena-sdk';
|
||||
|
||||
export interface DeviceResponse {
|
||||
[key: string]: any;
|
||||
@ -82,7 +83,7 @@ export class DeviceAPI {
|
||||
// Either return nothing, or throw an error with the info
|
||||
public async setTargetState(state: any): Promise<void> {
|
||||
const url = this.getUrlForAction('setTargetState');
|
||||
return DeviceAPI.promisifiedRequest(
|
||||
return await DeviceAPI.sendRequest(
|
||||
{
|
||||
method: 'POST',
|
||||
url,
|
||||
@ -96,7 +97,7 @@ export class DeviceAPI {
|
||||
public async getTargetState(): Promise<any> {
|
||||
const url = this.getUrlForAction('getTargetState');
|
||||
|
||||
return DeviceAPI.promisifiedRequest(
|
||||
return await DeviceAPI.sendRequest(
|
||||
{
|
||||
method: 'GET',
|
||||
url,
|
||||
@ -111,7 +112,7 @@ export class DeviceAPI {
|
||||
public async getDeviceInformation(): Promise<DeviceInfo> {
|
||||
const url = this.getUrlForAction('getDeviceInformation');
|
||||
|
||||
return DeviceAPI.promisifiedRequest(
|
||||
return await DeviceAPI.sendRequest(
|
||||
{
|
||||
method: 'GET',
|
||||
url,
|
||||
@ -126,7 +127,7 @@ export class DeviceAPI {
|
||||
public async getContainerId(serviceName: string): Promise<string> {
|
||||
const url = this.getUrlForAction('containerId');
|
||||
|
||||
const body = await DeviceAPI.promisifiedRequest(
|
||||
const body = await DeviceAPI.sendRequest(
|
||||
{
|
||||
method: 'GET',
|
||||
url,
|
||||
@ -149,7 +150,7 @@ export class DeviceAPI {
|
||||
public async ping(): Promise<void> {
|
||||
const url = this.getUrlForAction('ping');
|
||||
|
||||
return DeviceAPI.promisifiedRequest(
|
||||
return await DeviceAPI.sendRequest(
|
||||
{
|
||||
method: 'GET',
|
||||
url,
|
||||
@ -158,10 +159,10 @@ export class DeviceAPI {
|
||||
);
|
||||
}
|
||||
|
||||
public getVersion(): Promise<string> {
|
||||
public async getVersion(): Promise<string> {
|
||||
const url = this.getUrlForAction('version');
|
||||
|
||||
return DeviceAPI.promisifiedRequest({
|
||||
return await DeviceAPI.sendRequest({
|
||||
method: 'GET',
|
||||
url,
|
||||
json: true,
|
||||
@ -176,10 +177,10 @@ export class DeviceAPI {
|
||||
});
|
||||
}
|
||||
|
||||
public getStatus(): Promise<Status> {
|
||||
public async getStatus(): Promise<Status> {
|
||||
const url = this.getUrlForAction('status');
|
||||
|
||||
return DeviceAPI.promisifiedRequest({
|
||||
return await DeviceAPI.sendRequest({
|
||||
method: 'GET',
|
||||
url,
|
||||
json: true,
|
||||
@ -194,30 +195,33 @@ export class DeviceAPI {
|
||||
});
|
||||
}
|
||||
|
||||
public getLogStream(): Promise<Stream.Readable> {
|
||||
public async getLogStream(): Promise<Stream.Readable> {
|
||||
const url = this.getUrlForAction('logs');
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
return sdk.request.stream({ url });
|
||||
// Don't use the promisified version here as we want to stream the output
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = request.get(url);
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const stream = got.stream.get(url, { throwHttpErrors: false });
|
||||
|
||||
req.on('error', reject).on('response', async (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
reject(
|
||||
new ApiErrors.DeviceAPIError(
|
||||
'Non-200 response from log streaming endpoint',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
res.socket.setKeepAlive(true, 1000);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
// // stream
|
||||
// // .on('data', async () => {
|
||||
// // // if (res.statusCode !== 200) {
|
||||
// // // reject(
|
||||
// // // new ApiErrors.DeviceAPIError(
|
||||
// // // 'Non-200 response from log streaming endpoint',
|
||||
// // // ),
|
||||
// // // );
|
||||
// // // return;
|
||||
// // // }
|
||||
// // // try {
|
||||
// // // stream.socket.setKeepAlive(true, 1000);
|
||||
// // // } catch (error) {
|
||||
// // // reject(error);
|
||||
// // // }
|
||||
// // });
|
||||
// resolve(stream);
|
||||
// });
|
||||
}
|
||||
|
||||
private getUrlForAction(action: keyof typeof deviceEndpoints): string {
|
||||
@ -226,50 +230,33 @@ export class DeviceAPI {
|
||||
|
||||
// A helper method for promisifying general (non-streaming) requests. Streaming
|
||||
// requests should use a seperate setup
|
||||
private static async promisifiedRequest<
|
||||
T extends Parameters<typeof request>[0],
|
||||
>(opts: T, logger?: Logger): Promise<any> {
|
||||
interface ObjectWithUrl {
|
||||
url?: string;
|
||||
private static async sendRequest(
|
||||
opts: Parameters<BalenaSDK['request']['send']>[number],
|
||||
logger?: Logger,
|
||||
): Promise<any> {
|
||||
if (logger != null && opts.url != null) {
|
||||
logger.logDebug(`Sending request to ${opts.url}`);
|
||||
}
|
||||
|
||||
if (logger != null) {
|
||||
let url: string | null = null;
|
||||
if (_.isObject(opts) && (opts as ObjectWithUrl).url != null) {
|
||||
// the `as string` shouldn't be necessary, but the type system
|
||||
// is getting a little confused
|
||||
url = (opts as ObjectWithUrl).url as string;
|
||||
} else if (typeof opts === 'string') {
|
||||
url = opts;
|
||||
}
|
||||
|
||||
if (url != null) {
|
||||
logger.logDebug(`Sending request to ${url}`);
|
||||
}
|
||||
}
|
||||
const sdk = getBalenaSdk();
|
||||
|
||||
const doRequest = async () => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
return request(opts, (err, response, body) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
return resolve(body);
|
||||
case 400:
|
||||
return reject(
|
||||
new ApiErrors.BadRequestDeviceAPIError(body.message),
|
||||
);
|
||||
case 503:
|
||||
return reject(
|
||||
new ApiErrors.ServiceUnavailableAPIError(body.message),
|
||||
);
|
||||
default:
|
||||
return reject(new ApiErrors.DeviceAPIError(body.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
const response = await sdk.request.send(opts);
|
||||
|
||||
const bodyError =
|
||||
typeof response.body === 'string'
|
||||
? response.body
|
||||
: response.body.message;
|
||||
switch (response.statusCode) {
|
||||
case 200:
|
||||
return response.body;
|
||||
case 400:
|
||||
throw new ApiErrors.BadRequestDeviceAPIError(bodyError);
|
||||
case 503:
|
||||
throw new ApiErrors.ServiceUnavailableAPIError(bodyError);
|
||||
default:
|
||||
new ApiErrors.DeviceAPIError(bodyError);
|
||||
}
|
||||
};
|
||||
|
||||
return await retry({
|
||||
|
@ -94,7 +94,7 @@ async function installQemu(arch: string, qemuPath: string) {
|
||||
const urlVersion = encodeURIComponent(QEMU_VERSION);
|
||||
const qemuUrl = `https://github.com/balena-io/qemu/releases/download/${urlVersion}/${urlFile}`;
|
||||
|
||||
const request = await import('request');
|
||||
const { default: got } = await import('got');
|
||||
const fs = await import('fs');
|
||||
const zlib = await import('zlib');
|
||||
const tar = await import('tar-stream');
|
||||
@ -117,7 +117,8 @@ async function installQemu(arch: string, qemuPath: string) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
request(qemuUrl)
|
||||
got.stream
|
||||
.get(qemuUrl, { throwHttpErrors: false })
|
||||
.on('error', reject)
|
||||
.pipe(zlib.createGunzip())
|
||||
.on('error', reject)
|
||||
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||
import type { BalenaSDK } from 'balena-sdk';
|
||||
import * as JSONStream from 'JSONStream';
|
||||
import * as readline from 'readline';
|
||||
import * as request from 'request';
|
||||
import got from 'got';
|
||||
import type { RegistrySecrets } from '@balena/compose/dist/multibuild';
|
||||
import type * as Stream from 'stream';
|
||||
import streamToPromise = require('stream-to-promise');
|
||||
@ -27,6 +27,8 @@ import { tarDirectory } from './compose_ts';
|
||||
import { getVisuals, stripIndent } from './lazy';
|
||||
import Logger = require('./logger');
|
||||
|
||||
type GotStreamRequest = ReturnType<typeof got.stream.post>;
|
||||
|
||||
const globalLogger = Logger.getLogger();
|
||||
|
||||
const DEBUG_MODE = !!process.env.DEBUG;
|
||||
@ -119,7 +121,7 @@ export async function startRemoteBuild(
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
} finally {
|
||||
buildRequest.abort();
|
||||
buildRequest.destroy();
|
||||
const sigintErr = new SIGINTError('Build aborted on SIGINT signal');
|
||||
sigintErr.code = 'SIGINT';
|
||||
stream.emit('error', sigintErr);
|
||||
@ -336,32 +338,28 @@ async function getTarStream(build: RemoteBuild): Promise<Stream.Readable> {
|
||||
/**
|
||||
* Initiate a POST HTTP request to the remote builder and add some event
|
||||
* listeners.
|
||||
*
|
||||
* ¡! Note: this function must be synchronous because of a bug in the `request`
|
||||
* library that requires the following two steps to take place in the same
|
||||
* iteration of Node's event loop: (1) adding a listener for the 'response'
|
||||
* event and (2) calling request.pipe():
|
||||
* https://github.com/request/request/issues/887
|
||||
*/
|
||||
function createRemoteBuildRequest(
|
||||
build: RemoteBuild,
|
||||
tarStream: Stream.Readable,
|
||||
builderUrl: string,
|
||||
onError: (error: Error) => void,
|
||||
): request.Request {
|
||||
) {
|
||||
const zlib = require('zlib') as typeof import('zlib');
|
||||
if (DEBUG_MODE) {
|
||||
console.error(`[debug] Connecting to builder at ${builderUrl}`);
|
||||
}
|
||||
return request
|
||||
.post({
|
||||
url: builderUrl,
|
||||
auth: { bearer: build.auth },
|
||||
headers: { 'Content-Encoding': 'gzip' },
|
||||
return got.stream
|
||||
.post(builderUrl, {
|
||||
headers: {
|
||||
'Content-Encoding': 'gzip',
|
||||
Authorization: `Bearer ${build.auth}`,
|
||||
},
|
||||
body: tarStream.pipe(zlib.createGzip({ level: 6 })),
|
||||
throwHttpErrors: false,
|
||||
})
|
||||
.once('error', onError) // `.once` because the handler re-emits
|
||||
.once('response', (response: request.RequestResponse) => {
|
||||
.once('response', (response) => {
|
||||
if (response.statusCode >= 100 && response.statusCode < 400) {
|
||||
if (DEBUG_MODE) {
|
||||
console.error(
|
||||
@ -383,7 +381,7 @@ function createRemoteBuildRequest(
|
||||
|
||||
async function getRemoteBuildStream(
|
||||
build: RemoteBuild,
|
||||
): Promise<[request.Request, Stream.Stream]> {
|
||||
): Promise<[GotStreamRequest, Stream.Stream]> {
|
||||
const builderUrl = await getBuilderEndpoint(
|
||||
build.baseUrl,
|
||||
build.appSlug,
|
||||
|
14
npm-shrinkwrap.json
generated
14
npm-shrinkwrap.json
generated
@ -68,7 +68,6 @@
|
||||
"prettyjson": "^1.2.5",
|
||||
"progress-stream": "^2.0.0",
|
||||
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
|
||||
"request": "^2.88.2",
|
||||
"resin-cli-form": "^3.0.0",
|
||||
"resin-cli-visuals": "^2.0.0",
|
||||
"resin-doodles": "^0.2.0",
|
||||
@ -11120,11 +11119,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz",
|
||||
"integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==",
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz",
|
||||
"integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
@ -16391,9 +16389,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
@ -257,7 +257,6 @@
|
||||
"prettyjson": "^1.2.5",
|
||||
"progress-stream": "^2.0.0",
|
||||
"reconfix": "^1.0.0-v0-1-0-fork-46760acff4d165f5238bfac5e464256ef1944476",
|
||||
"request": "^2.88.2",
|
||||
"resin-cli-form": "^3.0.0",
|
||||
"resin-cli-visuals": "^2.0.0",
|
||||
"resin-doodles": "^0.2.0",
|
||||
|
@ -16,11 +16,11 @@
|
||||
*/
|
||||
|
||||
import * as chai from 'chai';
|
||||
import chaiAsPromised = require('chai-as-promised');
|
||||
import * as chaiAsPromised from 'chai-as-promised';
|
||||
import * as ejs from 'ejs';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as request from 'request';
|
||||
import got from 'got';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { LoginServer } from '../../build/auth/server';
|
||||
@ -67,32 +67,24 @@ describe('Login server:', function () {
|
||||
expectedStatusCode: number;
|
||||
expectedToken: string;
|
||||
urlPath?: string;
|
||||
verb?: string;
|
||||
verb?: 'get' | 'post';
|
||||
}) {
|
||||
opt.urlPath = opt.urlPath ?? addr.urlPath;
|
||||
const post = opt.verb
|
||||
? ((request as any)[opt.verb] as typeof request.post)
|
||||
: request.post;
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
post(
|
||||
`http://${addr.host}:${addr.port}${opt.urlPath}`,
|
||||
{
|
||||
form: {
|
||||
token: opt.expectedToken,
|
||||
},
|
||||
const request = opt.verb != null ? got[opt.verb] : got.post;
|
||||
const res = await request(
|
||||
`http://${addr.host}:${addr.port}${opt.urlPath}`,
|
||||
{
|
||||
form: {
|
||||
token: opt.expectedToken,
|
||||
},
|
||||
function (error, response, body) {
|
||||
try {
|
||||
expect(error).to.not.exist;
|
||||
expect(response.statusCode).to.equal(opt.expectedStatusCode);
|
||||
expect(body).to.equal(opt.expectedBody);
|
||||
resolve();
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
throwHttpErrors: false,
|
||||
// This ensures we can test the expected response in case we do that (a 404)
|
||||
allowGetBody: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(res.body).to.equal(opt.expectedBody);
|
||||
expect(res.statusCode).to.equal(opt.expectedStatusCode);
|
||||
|
||||
try {
|
||||
const token = await server.awaitForToken();
|
||||
|
Loading…
Reference in New Issue
Block a user