diff --git a/src/components/clusterprovider/azure/azureclusterprovider.ts b/src/components/clusterprovider/azure/azureclusterprovider.ts index dddca7b5f..37367eb72 100644 --- a/src/components/clusterprovider/azure/azureclusterprovider.ts +++ b/src/components/clusterprovider/azure/azureclusterprovider.ts @@ -1,4 +1,4 @@ -import * as restify from 'restify'; +import restify = require('restify'); import * as portfinder from 'portfinder'; import * as clusterproviderregistry from '../clusterproviderregistry'; import * as azure from './azure'; @@ -11,7 +11,8 @@ import { refreshExplorer } from '../common/explorer'; // TODO: de-globalise let wizardServer: restify.Server; -let wizardPort: number; +let wizardPort: number | undefined; +let registered = false; type HtmlRequestHandler = ( step: string | undefined, @@ -20,30 +21,45 @@ type HtmlRequestHandler = ( ) => Promise; export async function init(registry: clusterproviderregistry.ClusterProviderRegistry, context: azure.Context): Promise { - if (!wizardServer) { - wizardServer = restify.createServer({ - formatters: { - 'text/html': (req, resp, body) => body - } - }); + if (!registered) { + const serve = serveCallback(context); + registry.register({id: 'aks', displayName: "Azure Kubernetes Service", supportedActions: ['create', 'configure'], serve: serve}); + registry.register({id: 'acs', displayName: "Azure Container Service", supportedActions: ['create', 'configure'], serve: serve}); + registered = true; + } +} + +function serveCallback(context: azure.Context): () => Promise { + return () => serve(context); +} - wizardPort = await portfinder.getPortPromise({ port: 44000 }); +async function serve(context: azure.Context): Promise { + if (wizardPort) { + return wizardPort; + } - const htmlServer = new HtmlServer(context); + const restifyImpl: typeof restify = require('restify'); + wizardServer = restifyImpl.createServer({ + formatters: { + 'text/html': (req, resp, body) => body + } + }); - wizardServer.use(restify.plugins.queryParser(), restify.plugins.bodyParser()); - wizardServer.listen(wizardPort, '127.0.0.1'); + wizardPort = await portfinder.getPortPromise({ port: 44000 }); - // You MUST use fat arrow notation for the handler callbacks: passing the - // function reference directly will foul up the 'this' pointer. - wizardServer.get('/create', (req, resp, n) => htmlServer.handleGetCreate(req, resp, n)); - wizardServer.post('/create', (req, resp, n) => htmlServer.handlePostCreate(req, resp, n)); - wizardServer.get('/configure', (req, resp, n) => htmlServer.handleGetConfigure(req, resp, n)); - wizardServer.post('/configure', (req, resp, n) => htmlServer.handlePostConfigure(req, resp, n)); + const htmlServer = new HtmlServer(context); - registry.register({id: 'aks', displayName: "Azure Kubernetes Service", port: wizardPort, supportedActions: ['create', 'configure']}); - registry.register({id: 'acs', displayName: "Azure Container Service", port: wizardPort, supportedActions: ['create', 'configure']}); - } + wizardServer.use(restifyImpl.plugins.queryParser(), restifyImpl.plugins.bodyParser()); + wizardServer.listen(wizardPort, '127.0.0.1'); + + // You MUST use fat arrow notation for the handler callbacks: passing the + // function reference directly will foul up the 'this' pointer. + wizardServer.get('/create', (req, resp, n) => htmlServer.handleGetCreate(req, resp, n)); + wizardServer.post('/create', (req, resp, n) => htmlServer.handlePostCreate(req, resp, n)); + wizardServer.get('/configure', (req, resp, n) => htmlServer.handleGetConfigure(req, resp, n)); + wizardServer.post('/configure', (req, resp, n) => htmlServer.handlePostConfigure(req, resp, n)); + + return wizardPort; } class HtmlServer { diff --git a/src/components/clusterprovider/clusterproviderregistry.ts b/src/components/clusterprovider/clusterproviderregistry.ts index 61aebe0b9..2d59272f5 100644 --- a/src/components/clusterprovider/clusterproviderregistry.ts +++ b/src/components/clusterprovider/clusterproviderregistry.ts @@ -3,8 +3,8 @@ export type ClusterProviderAction = 'create' | 'configure'; export interface ClusterProvider { readonly id: string; readonly displayName: string; - readonly port: number; readonly supportedActions: ClusterProviderAction[]; + serve(): Promise; } export interface ClusterProviderRegistry { @@ -16,7 +16,7 @@ class RegistryImpl implements ClusterProviderRegistry { private readonly providers = new Array(); public register(clusterProvider: ClusterProvider) { - console.log(`You registered ${clusterProvider.id} for port ${clusterProvider.port}`); + console.log(`You registered cluster type ${clusterProvider.id}`); this.providers.push(clusterProvider); } diff --git a/src/components/clusterprovider/clusterproviderserver.ts b/src/components/clusterprovider/clusterproviderserver.ts index b76ff3d2a..d988e9f78 100644 --- a/src/components/clusterprovider/clusterproviderserver.ts +++ b/src/components/clusterprovider/clusterproviderserver.ts @@ -1,4 +1,4 @@ -import * as restify from 'restify'; +import restify = require('restify'); import * as portfinder from 'portfinder'; import * as clusterproviderregistry from './clusterproviderregistry'; import { styles, script, waitScript } from '../../wizard'; @@ -8,8 +8,9 @@ let cpServer: restify.Server; let cpPort: number; export async function init(): Promise { + const restifyImpl: typeof restify = require('restify'); if (!cpServer) { - cpServer = restify.createServer({ + cpServer = restifyImpl.createServer({ formatters: { 'text/html': (req, resp, body) => body } @@ -17,7 +18,7 @@ export async function init(): Promise { cpPort = await portfinder.getPortPromise({ port: 44000 }); - cpServer.use(restify.plugins.queryParser()); + cpServer.use(restifyImpl.plugins.queryParser()); cpServer.listen(cpPort, '127.0.0.1'); cpServer.get('/', handleRequest); } @@ -27,10 +28,10 @@ export function url(action: clusterproviderregistry.ClusterProviderAction): stri return `http://localhost:${cpPort}/?action=${action}`; } -function handleRequest(request: restify.Request, response: restify.Response, next: restify.Next): void { +async function handleRequest(request: restify.Request, response: restify.Response, next: restify.Next): Promise { const clusterType = request.query['clusterType']; if (clusterType) { - handleClusterTypeSelection(request, response, next); + await handleClusterTypeSelection(request, response, next); } else { handleGetProviderList(request, response, next); } @@ -46,14 +47,16 @@ function handleGetProviderList(request: restify.Request, response: restify.Respo next(); } -function handleClusterTypeSelection(request: restify.Request, response: restify.Response, next: restify.Next): void { +async function handleClusterTypeSelection(request: restify.Request, response: restify.Response, next: restify.Next): Promise { const clusterType = request.query['clusterType']; const action = request.query["action"]; reporter.sendTelemetryEvent("cloudselection", { action: action, clusterType: clusterType }); const clusterProvider = clusterproviderregistry.get().list().find((cp) => cp.id === clusterType); // TODO: move into clusterproviderregistry - const url = `http://localhost:${clusterProvider.port}/${action}?clusterType=${clusterProvider.id}`; + const port = await clusterProvider.serve(); + console.log(`${clusterProvider.id} wizard serving on port ${port}`); + const url = `http://localhost:${port}/${action}?clusterType=${clusterProvider.id}`; response.redirect(307, url, next); } diff --git a/src/components/clusterprovider/minikube/minikubeclusterprovider.ts b/src/components/clusterprovider/minikube/minikubeclusterprovider.ts index b3a7b6609..ced03f96c 100644 --- a/src/components/clusterprovider/minikube/minikubeclusterprovider.ts +++ b/src/components/clusterprovider/minikube/minikubeclusterprovider.ts @@ -1,4 +1,4 @@ -import * as restify from 'restify'; +import restify = require('restify'); import * as portfinder from 'portfinder'; import * as vscode from 'vscode'; import * as clusterproviderregistry from '../clusterproviderregistry'; @@ -19,32 +19,49 @@ type HtmlRequestHandler = ( ) => Promise; let minikubeWizardServer: restify.Server; +let minikubeWizardPort: number | undefined; +let registered = false; export async function init(registry: clusterproviderregistry.ClusterProviderRegistry, context: Context): Promise { - if (!minikubeWizardServer) { - minikubeWizardServer = restify.createServer({ - formatters: { - 'text/html': (req, resp, body) => body - } - }); - - const port = await portfinder.getPortPromise({ port: 44000 }); - - const htmlServer = new HtmlServer(context); - minikubeWizardServer.use(restify.plugins.queryParser(), restify.plugins.bodyParser()); - minikubeWizardServer.listen(port, '127.0.0.1'); - - // You MUST use fat arrow notation for the handler callbacks: passing the - // function reference directly will foul up the 'this' pointer. - minikubeWizardServer.get('/create', (req, resp, n) => htmlServer.handleGetCreate(req, resp, n)); - minikubeWizardServer.post('/create', (req, resp, n) => htmlServer.handlePostCreate(req, resp, n)); - minikubeWizardServer.get('/configure', (req, resp, n) => htmlServer.handleGetConfigure(req, resp, n)); - minikubeWizardServer.post('/configure', (req, resp, n) => htmlServer.handlePostConfigure(req, resp, n)); - - registry.register({id: 'minikube', displayName: "Minikube local cluster", port: port, supportedActions: ['create', 'configure']}); + if (!registered) { + const serve = serveCallback(context); + registry.register({id: 'minikube', displayName: "Minikube local cluster", supportedActions: ['create', 'configure'], serve: serve}); + registered = true; } } +function serveCallback(context: Context): () => Promise { + return () => serve(context); +} + +async function serve(context: Context): Promise { + if (minikubeWizardPort) { + return minikubeWizardPort; + } + + const restifyImpl: typeof restify = require('restify'); + minikubeWizardServer = restifyImpl.createServer({ + formatters: { + 'text/html': (req, resp, body) => body + } + }); + + minikubeWizardPort = await portfinder.getPortPromise({ port: 44000 }); + + const htmlServer = new HtmlServer(context); + minikubeWizardServer.use(restifyImpl.plugins.queryParser(), restifyImpl.plugins.bodyParser()); + minikubeWizardServer.listen(minikubeWizardPort, '127.0.0.1'); + + // You MUST use fat arrow notation for the handler callbacks: passing the + // function reference directly will foul up the 'this' pointer. + minikubeWizardServer.get('/create', (req, resp, n) => htmlServer.handleGetCreate(req, resp, n)); + minikubeWizardServer.post('/create', (req, resp, n) => htmlServer.handlePostCreate(req, resp, n)); + minikubeWizardServer.get('/configure', (req, resp, n) => htmlServer.handleGetConfigure(req, resp, n)); + minikubeWizardServer.post('/configure', (req, resp, n) => htmlServer.handlePostConfigure(req, resp, n)); + + return minikubeWizardPort; +} + class HtmlServer { constructor(readonly context: Context) {} diff --git a/src/components/download/download.ts b/src/components/download/download.ts index 87ee48e37..76ac59e25 100644 --- a/src/components/download/download.ts +++ b/src/components/download/download.ts @@ -1,24 +1,27 @@ -const home = process.env['HOME']; - -import * as download_core from 'download'; // NEVER do a naked import of this as it corrupts $HOME on Windows - use wrapper functions from this module (or export the download function from this module if you really need the flexibility of the core implementation) import * as path from 'path'; import * as stream from 'stream'; import * as tmp from 'tmp'; import { succeeded, Errorable } from '../../errorable'; -// Fix download module corrupting HOME environment variable on Windows -// See https://github.com/Azure/vscode-kubernetes-tools/pull/302#issuecomment-404678781 -// and https://github.com/kevva/npm-conf/issues/13 -if (home) { - process.env['HOME'] = home; -} - type DownloadFunc = (url: string, destination?: string, options?: any) => Promise & stream.Duplex; // Stream has additional events - see https://www.npmjs.com/package/download -const download: DownloadFunc = download_core; +let download: DownloadFunc; + +function ensureDownloadFunc() { + if (!download) { + // Fix download module corrupting HOME environment variable on Windows + // See https://github.com/Azure/vscode-kubernetes-tools/pull/302#issuecomment-404678781 + // and https://github.com/kevva/npm-conf/issues/13 + const home = process.env['HOME']; + download = require('download'); + if (home) { + process.env['HOME'] = home; + } + } +} export async function toTempFile(sourceUrl: string): Promise> { const tempFileObj = tmp.fileSync({ prefix: "vsk-autoinstall-" }); @@ -30,6 +33,7 @@ export async function toTempFile(sourceUrl: string): Promise> } export async function to(sourceUrl: string, destinationFile: string): Promise> { + ensureDownloadFunc(); try { await download(sourceUrl, path.dirname(destinationFile), { filename: path.basename(destinationFile) }); return { succeeded: true, result: null }; diff --git a/src/explainer.ts b/src/explainer.ts index ba47ebd02..54d20455e 100644 --- a/src/explainer.ts +++ b/src/explainer.ts @@ -1,6 +1,6 @@ 'use strict'; -import * as k8s from 'k8s'; +import k8s = require('k8s'); import * as pluralize from 'pluralize'; import * as kubeconfig from './kubeconfig'; import { formatComplex, formatOne, Typed, formatType } from "./schema-formatting"; @@ -16,8 +16,9 @@ function fixUrl(kapi: any /* deliberately bypass type checks as .domain is inter } function readSwaggerCore(kc: kubeconfig.KubeConfig): Promise { + const k8sImpl: typeof k8s = require('k8s'); return new Promise((resolve, reject) => { - const kapi = k8s.api(apiCredentials(kc)); + const kapi = k8sImpl.api(apiCredentials(kc)); fixUrl(kapi); kapi.get('swagger.json', (err, data) => { if (err) {