node-oracledb/package/oracledbinstall.js

498 lines
16 KiB
JavaScript

/* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. */
/******************************************************************************
*
* You may not use the identified files except in compliance with the Apache
* License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*
* NAME
* oracledbinstall.js
*
* DESCRIPTION
* This script is included in the npm bundle of node-oracledb. It
* is invoked by package.json during npm install. It downloads a
* pre-built node-oracleb binary from GitHub if one is available, or
* gives a message on how to compile one from source code.
*
* Set NODE_ORACLEDB_TRACE_INSTALL=TRUE for installation trace output.
*
* MAINTENANCE NOTES
* - This file should run with Node 4 or later.
* - This file should only ever 'require' packages included in core Node.js.
*
*****************************************************************************/
'use strict';
const http = require('http'); // Fails in old Node.js. Use Node 4+
const https = require('https');
const fs = require('fs');
const url = require('url');
const packageUtil = require('./util.js');
const execSync = require('child_process').execSync;
packageUtil.initDynamicProps();
try {
// Requiring here to ensure it's available, actually used in util.js
const crypto = require('crypto');
} catch (err) {
done(new Error('Node.js crypto module required to install from binary'));
return;
}
// Note: the Makefile uses these hostname and path values for the npm
// package but will substitute them for the staging package
const PACKAGE_HOSTNAME = 'github.com';
const PACKAGE_PATH_REMOTE = '/oracle/node-oracledb/releases/download/' + packageUtil.dynamicProps.GITHUB_TAG + '/' + packageUtil.dynamicProps.PACKAGE_FILE_NAME;
const SHA_PATH_REMOTE = '/oracle/node-oracledb/releases/download/' + packageUtil.dynamicProps.GITHUB_TAG + '/' + packageUtil.SHA_FILE_NAME;
const PORT = 443;
// readProxyUrl gets the proxy from the environment or npm config.
// Note getting npm config can be slow on some environments (https://github.com/npm/npm/issues/14458)
// so setting a proxy environment variable is preferred.
function readProxyUrl() {
packageUtil.trace('In readProxyUrl');
const envProxy = process.env.https_proxy ||
process.env.HTTPS_PROXY ||
process.env.http_proxy ||
process.env.HTTP_PROXY ||
process.env.all_proxy ||
process.env.ALL_PROXY;
if (envProxy) {
return envProxy;
}
// 'npm config get' can be slow, so call it after checking the env
// variables: https://github.com/npm/npm/issues/14458
const httpsProxy = execSync('npm config get https-proxy').toString().trim();
if (httpsProxy !== 'null') {
return httpsProxy;
}
const httpProxy = execSync('npm config get proxy').toString().trim();
if (httpProxy !== 'null') {
return httpProxy;
}
return undefined;
}
// getProxyConfig gets the proxy configuration for a given hostname.
// Has basic no_proxy support.
function getProxyConfig(hostname) {
packageUtil.trace('In getProxyConfig', hostname);
const proxyConfig = {
useProxy: undefined
};
let proxy = readProxyUrl();
if (proxy) {
proxyConfig.useProxy = true;
if (!proxy.startsWith('http://') && !proxy.startsWith('https://')) {
proxy = 'https://' + proxy;
}
const parsedUrl = url.parse(proxy);
proxyConfig.hostname = parsedUrl.hostname;
proxyConfig.port = parsedUrl.port;
proxyConfig.auth = parsedUrl.auth;
} else {
proxyConfig.useProxy = false;
return proxyConfig;
}
const noProxy = process.env.NO_PROXY ||
process.env.no_PROXY ||
process.env.no_proxy;
if (noProxy === '*') {
packageUtil.trace('noProxy wildcard');
proxyConfig.useProxy = false;
proxyConfig.hostname = undefined;
proxyConfig.hostname = undefined;
} else if (noProxy) {
const noProxies = noProxy.toLowerCase().split(',');
packageUtil.trace('noProxy', noProxies);
if (noProxies.indexOf(hostname.toLowerCase()) > -1) {
proxyConfig.useProxy = false;
proxyConfig.hostname = undefined;
proxyConfig.port = undefined;
}
}
return proxyConfig;
}
// verifyBinary is used to ensure that the SHA of the local binary matches the
// SHA in the remote SHA file.
function verifyBinary() {
return new Promise((resolve, reject) => {
packageUtil.trace('In verifyBinary');
packageUtil.trace('Checking for binary at', packageUtil.BINARY_PATH_LOCAL);
packageUtil.log('Verifying installation');
if (!fs.existsSync(packageUtil.BINARY_PATH_LOCAL)) {
resolve(false);
return;
}
let remoteShaFile = '';
let binarySha;
packageUtil.getSha(packageUtil.BINARY_PATH_LOCAL)
.then((sha) => {
binarySha = sha;
return getRemoteFileReadStream(PACKAGE_HOSTNAME, SHA_PATH_REMOTE);
})
.then(readStream => {
return new Promise((resolve, reject) => {
readStream.setEncoding('utf8');
// Buffer file in memory
readStream.on('data', chunk => {
remoteShaFile += chunk;
});
readStream.on('error', reject);
readStream.on('end', resolve);
});
})
.then(() => {
if (!remoteShaFile.match(packageUtil.dynamicProps.BINARY_BUILD_NAME)) {
packageUtil.log('Build not found in SHASUMS256.txt');
resolve(false);
} else if (!remoteShaFile.match(binarySha + ' ' + packageUtil.dynamicProps.BINARY_BUILD_NAME)) {
packageUtil.log('Binary SHA does not match SHA in SHASUMS256.txt');
resolve(false);
} else {
packageUtil.log('Binary SHA matches SHA in SHASUMS256.txt');
resolve(true);
}
})
.catch(reject);
});
}
// The getRemoteFileReadStream function is used as the starting point for
// fetching remote file streams. It checks the proxy configuration and
// routes the request to the right function accordingly.
function getRemoteFileReadStream(hostname, path) {
packageUtil.trace('In getRemoteFileReadStream', hostname, path);
const proxyConfig = getProxyConfig(hostname);
if (proxyConfig.useProxy) {
return getFileReadStreamByProxy(hostname, path, proxyConfig.hostname, proxyConfig.port, proxyConfig.auth);
} else {
return getFileReadStreamBase(hostname, path);
}
}
// getFileReadStreamByProxy connects to a proxy server before calling getFileReadStreamBase
// to retrieve a remote file read stream.
function getFileReadStreamByProxy(hostname, path, proxyHostname, proxyPort, auth) {
return new Promise((resolve, reject) => {
packageUtil.trace('In getFileReadStreamByProxy', hostname, path, proxyHostname, proxyPort);
let headers = {
'host': hostname + ':' + PORT
};
if (auth) {
headers['Proxy-Authorization'] = 'Basic ' + new Buffer(auth).toString('base64');
}
// Open a proxy tunnel
const req = http.request({
host: proxyHostname,
port: proxyPort,
method: 'CONNECT',
path: hostname + ':' + PORT,
headers: headers
});
req.on('error', reject);
// When this ends, the transfer will be complete
req.end();
req.on('connect', function(res, socket) {
if (res.statusCode >= 300 && res.statusCode < 400) { // warning: proxy redirection code is untested
const redirectUrl = url.parse(res.headers.location);
proxyHostname = redirectUrl.hostname;
proxyPort = redirectUrl.port;
return getFileReadStreamByProxy(hostname, path, proxyHostname, proxyPort, auth);
} else if (res.statusCode !== 200) {
reject(new Error('Error: HTTP proxy request for ' + hostname + path + ' failed with code ' + res.statusCode));
return;
} else {
getFileReadStreamBase(hostname, path, socket)
.then(fileReadStream => {
resolve(fileReadStream);
})
.catch(reject);
}
});
});
}
// The getFileReadStreamBase function is the main function that retrieves a remote
// file read stream.
function getFileReadStreamBase(hostname, path, socket) {
return new Promise((resolve, reject) => {
packageUtil.trace('In getFileReadStreamBase', hostname, path);
let settled = false;
const req = https.get(
{
host: hostname,
path: path,
socket: socket
},
function(res) {
packageUtil.trace('HTTP statusCode =', res.statusCode);
if (res.statusCode >= 300 && res.statusCode < 400) {
const redirectUrl = url.parse(res.headers.location);
const newHostname = redirectUrl.hostname;
const newPath = redirectUrl.pathname + redirectUrl.search;
getRemoteFileReadStream(newHostname, newPath)
.then(res => {
if (!settled) {
resolve(res);
settled = true;
}
})
.catch(err => {
if (!settled) {
reject(err);
settled = true;
}
});
} else if (res.statusCode !== 200) {
if (!settled) {
reject(new Error('Error: HTTPS request for https://' + hostname + path + ' failed with code ' + res.statusCode));
settled = true;
}
} else {
if (!settled) {
resolve(res);
settled = true;
}
}
}
);
req.on('error', err => {
if (!settled) {
reject(err);
settled = true;
}
});
});
}
// installBinary creates the directories for the binary, downloads the custom
// file, and then extracts the license and the binary.
function installBinary() {
return new Promise((resolve, reject) => {
packageUtil.trace('In installBinary');
// Directories to be created for the binary
const dirs = [
'build',
'build/Release'
];
// Create relative binary directory
try {
dirs.forEach(function(p) {
if (!fs.existsSync(p)) {
fs.mkdirSync(p);
fs.chmodSync(p, '0755');
}
});
} catch(err) {
if (err) {
reject(err);
return;
}
}
// Get the binary
getRemoteFileReadStream(PACKAGE_HOSTNAME, PACKAGE_PATH_REMOTE)
.then(compressedReadstream => {
return packageUtil.extract({
licenseDest: packageUtil.LICENSE_PATH_LOCAL,
writeLicense: true,
binaryDest: packageUtil.BINARY_PATH_LOCAL,
compressedReadstream: compressedReadstream
});
})
.then(() => {
fs.chmodSync(packageUtil.BINARY_PATH_LOCAL, '0755');
resolve();
})
.catch(err => {
reject(err);
});
});
}
// The done function is used to print concluding messages and quit.
function done(err, alreadyInstalled) {
const installUrl = 'https://oracle.github.io/node-oracledb/INSTALL.html';
if (err) {
packageUtil.error('NJS-054: Binary build/Release/oracledb.node was not installed.');
if (['darwin', 'win32', 'linux'].indexOf(process.platform) < 0) {
packageUtil.error('Pre-built binary packages are not available for platform="' + process.platform + '"');
} else if (process.arch !== 'x64') {
packageUtil.error('Pre-built binary packages are not available for architecture="' + process.arch + '"');
} else if (['48', '57', '64'].indexOf(process.versions.modules) < 0) {
packageUtil.error('Pre-built binary packages are not available for this version of Node.js (NODE_MODULE_VERSION="' + process.versions.modules + '")');
}
packageUtil.error('Failed to install binary package ' + packageUtil.dynamicProps.PACKAGE_FILE_NAME);
packageUtil.error(err.message);
packageUtil.error('For help see ' + installUrl + '#troubleshooting\n');
process.exit(87);
} else {
let arch;
let clientUrl;
if (process.arch === 'x64') {
arch = '64-bit';
} else {
arch = '32-bit';
}
packageUtil.log('');
packageUtil.log('********************************************************************************');
if (alreadyInstalled) {
packageUtil.log('** Node-oracledb ' + packageUtil.dynamicProps.PACKAGE_JSON_VERSION + ' was already installed for Node.js ' + process.versions.node + ' (' + process.platform + ', ' + process.arch +')');
} else {
packageUtil.log('** Node-oracledb ' + packageUtil.dynamicProps.PACKAGE_JSON_VERSION + ' installation complete for Node.js ' + process.versions.node + ' (' + process.platform + ', ' + process.arch +')');
}
packageUtil.log('**');
packageUtil.log('** To use the installed node-oracledb:');
if (process.platform === 'linux') {
if (process.arch === 'x64') {
clientUrl = 'http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html';
} else {
clientUrl = 'http://www.oracle.com/technetwork/topics/linuxsoft-082809.html';
}
packageUtil.log('** - You must have ' + arch + ' Oracle client libraries in LD_LIBRARY_PATH, or configured with ldconfig');
packageUtil.log('** - If you do not already have libraries, install the Instant Client Basic or Basic Light package from ');
packageUtil.log('** ' + clientUrl);
} else if (process.platform === 'darwin') {
clientUrl = 'http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html';
packageUtil.log('** - You need to have the Oracle Instant Client Basic or Basic Light package in ~/lib or /usr/local/lib');
packageUtil.log('** Download from ' + clientUrl);
} else if (process.platform === 'win32') {
if (process.arch === 'x64') {
clientUrl = 'http://www.oracle.com/technetwork/topics/winx64soft-089540.html';
} else {
clientUrl = 'http://www.oracle.com/technetwork/topics/winsoft-085727.html';
}
packageUtil.log('** - You must have ' + arch + ' Oracle client libraries in your PATH environment variable');
packageUtil.log('** - If you do not already have libraries, install the Instant Client Basic or Basic Light package from');
packageUtil.log('** ' + clientUrl);
packageUtil.log('** - A Microsoft Visual Studio Redistributable suitable for your Oracle client library version must be available');
packageUtil.log('** Check ' + installUrl + ' for details');
} else {
clientUrl = 'http://www.oracle.com/technetwork/database/database-technologies/instant-client/overview/index.html';
packageUtil.log('** - You must have ' + arch + ' Oracle client libraries in your operating system library search path');
packageUtil.log('** - If you do not already have libraries, install an Instant Client Basic or Basic Light package from: ');
packageUtil.log('** ' + clientUrl);
}
packageUtil.log('**');
packageUtil.log('** Node-oracledb installation instructions: ' + installUrl);
packageUtil.log('********************************************************************************\n');
}
}
// The install function is the main function that installs the binary.
function install() {
packageUtil.trace('In install');
const nodeMajorVersion = Number(process.version.split('.')[0].replace(/^v/, ''));
if (!nodeMajorVersion >= 4) {
done(new Error('Node.js v4.0.0 or higher is required to install from binary'));
return;
}
packageUtil.log('Beginning installation');
verifyBinary() // check if download is necessary for 'npm rebuild'
.then((valid) => {
if (valid) {
done(null, true);
} else {
packageUtil.log('Continuing installation');
return new Promise((resolve, reject) => {
installBinary()
.then(() => {
packageUtil.log('Oracledb downloaded');
return verifyBinary();
})
.then(valid => {
if (valid) {
done(null, false);
resolve();
} else {
reject(new Error('Verification failed'));
}
})
.catch(reject);
});
}
})
.catch(err => {
done(err, false);
});
}
install();