Don't output EXPOSE if empty port specified (#490)

* Don't output EXPOSE if empty port specified

* Tests

* PR fix

* Lint and fix
This commit is contained in:
Stephen Weatherford (MSFT) 2018-09-21 17:48:28 -07:00 коммит произвёл GitHub
Родитель 82abf3c848
Коммит 7227ce4c95
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 134 добавлений и 46 удалений

Просмотреть файл

@ -3,6 +3,7 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { isNumber } from 'util';
import vscode = require('vscode');
import { IAzureQuickPickItem, IAzureUserInput } from 'vscode-azureextensionui';
import { ext } from "../extensionVariables";
@ -22,11 +23,18 @@ export type Platform =
* Prompts for a port number
* @throws `UserCancelledError` if the user cancels.
*/
export async function promptForPort(port: number): Promise<string> {
export async function promptForPort(port: string): Promise<string> {
let opt: vscode.InputBoxOptions = {
placeHolder: `${port}`,
prompt: 'What port does your app listen on?',
value: `${port}`
prompt: 'What port does your app listen on? ENTER for none.',
value: `${port}`,
validateInput: (value: string): string | undefined => {
if (value && (!Number.isInteger(Number(value)) || Number(value) <= 0)) {
return 'Port must be a positive integer or else empty for no exposed port';
}
return undefined;
}
}
return ext.ui.showInputBox(opt);

Просмотреть файл

@ -53,11 +53,18 @@ export type ConfigureTelemetryProperties = {
packageFileSubfolderDepth?: string; // 0 = project/etc file in root folder, 1 = in subfolder, 2 = in subfolder of subfolder, etc.
};
const generatorsByPlatform = new Map<Platform, {
export interface IPlatformGeneratorInfo {
genDockerFile: GeneratorFunction,
genDockerCompose: GeneratorFunction,
genDockerComposeDebug: GeneratorFunction
}>();
genDockerComposeDebug: GeneratorFunction,
defaultPort: string
}
export function getExposeStatements(port: string): string {
return port ? `EXPOSE ${port}` : '';
}
const generatorsByPlatform = new Map<Platform, IPlatformGeneratorInfo>();
generatorsByPlatform.set('ASP.NET Core', configureAspDotNetCore);
generatorsByPlatform.set('Go', configureGo);
generatorsByPlatform.set('Java', configureJava);
@ -70,7 +77,14 @@ function genDockerFile(serviceNameAndRelativePath: string, platform: Platform, o
let generators = generatorsByPlatform.get(platform);
assert(generators, `Could not find dockerfile generator functions for "${platform}"`);
if (generators.genDockerFile) {
return generators.genDockerFile(serviceNameAndRelativePath, platform, os, port, { cmd, author, version, artifactName });
let contents = generators.genDockerFile(serviceNameAndRelativePath, platform, os, port, { cmd, author, version, artifactName });
// Remove multiple empty lines with single empty lines, as might be produced
// if $expose_statements$ or another template variable is an empty string
contents = contents.replace(/(\r\n){3}/g, "\r\n\r\n")
.replace(/(\n){3}/g, "\n\n");
return contents;
}
}
@ -325,6 +339,7 @@ async function configureCore(actionContext: IActionContext, options: ConfigureAp
const platformType: Platform = options.platform || await quickPickPlatform();
properties.configurePlatform = platformType;
let generatorInfo = generatorsByPlatform.get(platformType);
let os: OS | undefined = options.os;
if (!os && platformType.toLowerCase().includes('.net')) {
@ -334,11 +349,7 @@ async function configureCore(actionContext: IActionContext, options: ConfigureAp
let port: string | undefined = options.port;
if (!port) {
if (platformType.toLowerCase().includes('.net')) {
port = await promptForPort(80);
} else {
port = await promptForPort(3000);
}
port = await promptForPort(generatorInfo.defaultPort);
}
let targetFramework: string;

Просмотреть файл

@ -10,18 +10,23 @@ import * as semver from 'semver';
import { extractRegExGroups } from '../helpers/extractRegExGroups';
import { isWindows, isWindows10RS3OrNewer, isWindows10RS4OrNewer } from '../helpers/windowsVersion';
import { OS, Platform } from './config-utils';
import { PackageInfo } from './configure';
import { getExposeStatements, IPlatformGeneratorInfo, PackageInfo } from './configure';
// This file handles both ASP.NET core and .NET Core Console
let configureDotNetCore = {
export const configureAspDotNetCore: IPlatformGeneratorInfo = {
genDockerFile,
genDockerCompose: undefined, // We don't generate compose files for .net core
genDockerComposeDebug: undefined // We don't generate compose files for .net core
genDockerComposeDebug: undefined, // We don't generate compose files for .net core
defaultPort: '80'
};
export let configureAspDotNetCore = configureDotNetCore;
export let configureDotNetCoreConsole = configureDotNetCore;
export const configureDotNetCoreConsole: IPlatformGeneratorInfo = {
genDockerFile,
genDockerCompose: undefined, // We don't generate compose files for .net core
genDockerComposeDebug: undefined, // We don't generate compose files for .net core
defaultPort: ''
};
const AspNetCoreRuntimeImageFormat = "microsoft/aspnetcore:{0}.{1}{2}";
const AspNetCoreSdkImageFormat = "microsoft/aspnetcore-build:{0}.{1}{2}";
@ -164,7 +169,7 @@ function genDockerFile(serviceNameAndRelativePath: string, platform: Platform, o
let assemblyNameNoExtension = serviceName;
// example: COPY Core2.0ConsoleAppWindows/Core2.0ConsoleAppWindows.csproj Core2.0ConsoleAppWindows/
let copyProjectCommands = `COPY ["${serviceNameAndRelativePath}.csproj", "${projectDirectory}/"]`
let exposeStatements = port ? `EXPOSE ${port}` : '';
let exposeStatements = getExposeStatements(port);
// Parse version from TargetFramework
// Example: netcoreapp1.0
@ -226,11 +231,6 @@ function genDockerFile(serviceNameAndRelativePath: string, platform: Platform, o
.replace(/\$assembly_name\$/g, assemblyNameNoExtension)
.replace(/\$copy_project_commands\$/g, copyProjectCommands);
// Remove multiple empty lines with single empty lines, as might be produced
// if $expose_statements$ or another template variable is an empty string
contents = contents.replace(/(\r\n){3}/g, "\r\n\r\n")
.replace(/(\n){3}/g, "\n\n");
let unreplacedToken = extractRegExGroups(contents, /(\$[a-z_]+\$)/, ['']);
if (unreplacedToken[0]) {
assert.fail(`Unreplaced template token "${unreplacedToken}"`);

Просмотреть файл

@ -3,15 +3,18 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { PackageInfo } from './configure';
import { getExposeStatements, IPlatformGeneratorInfo, PackageInfo } from './configure';
export let configureGo = {
export let configureGo: IPlatformGeneratorInfo = {
genDockerFile,
genDockerCompose,
genDockerComposeDebug
genDockerComposeDebug,
defaultPort: '3000'
};
function genDockerFile(serviceNameAndRelativePath: string, platform: string, os: string | undefined, port: string, { cmd, author, version, artifactName }: Partial<PackageInfo>): string {
let exposeStatements = getExposeStatements(port);
return `
#build stage
FROM golang:alpine AS builder
@ -27,7 +30,7 @@ RUN apk --no-cache add ca-certificates
COPY --from=builder /go/bin/app /app
ENTRYPOINT ./app
LABEL Name=${serviceNameAndRelativePath} Version=${version}
EXPOSE ${port}
${exposeStatements}
`;
}

Просмотреть файл

@ -3,24 +3,26 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { PackageInfo } from './configure';
import { getExposeStatements, IPlatformGeneratorInfo, PackageInfo } from './configure';
export let configureJava = {
export let configureJava: IPlatformGeneratorInfo = {
genDockerFile,
genDockerCompose,
genDockerComposeDebug
genDockerComposeDebug,
defaultPort: '3000'
};
function genDockerFile(serviceNameAndRelativePath: string, platform: string, os: string | undefined, port: string, { cmd, author, version, artifactName }: Partial<PackageInfo>): string {
let exposeStatements = getExposeStatements(port);
const artifact = artifactName ? artifactName : `${serviceNameAndRelativePath}.jar`;
return `
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAVA_OPTS
ENV JAVA_OPTS=$JAVA_OPTS
ADD ${artifact} ${serviceNameAndRelativePath}.jar
EXPOSE ${port}
${exposeStatements}
ENTRYPOINT exec java $JAVA_OPTS -jar ${serviceNameAndRelativePath}.jar
# For Spring-Boot project, use the entrypoint below to reduce Tomcat startup time.
#ENTRYPOINT exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar ${serviceNameAndRelativePath}.jar

Просмотреть файл

@ -3,22 +3,25 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { PackageInfo } from './configure';
import { getExposeStatements, IPlatformGeneratorInfo, PackageInfo } from './configure';
export let configureNode = {
export let configureNode: IPlatformGeneratorInfo = {
genDockerFile,
genDockerCompose,
genDockerComposeDebug
genDockerComposeDebug,
defaultPort: '3000'
};
function genDockerFile(serviceNameAndRelativePath: string, platform: string, os: string | undefined, port: string, { cmd, author, version, artifactName }: Partial<PackageInfo>): string {
let exposeStatements = getExposeStatements(port);
return `FROM node:8.9-alpine
ENV NODE_ENV production
WORKDIR /usr/src/app
COPY ["package.json", "package-lock.json*", "npm-shrinkwrap.json*", "./"]
RUN npm install --production --silent && mv node_modules ../
COPY . .
EXPOSE ${port}
${exposeStatements}
CMD ${cmd}`;
}

Просмотреть файл

@ -3,15 +3,18 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { PackageInfo } from './configure';
import { getExposeStatements, IPlatformGeneratorInfo, PackageInfo } from './configure';
export let configurePython = {
export let configurePython: IPlatformGeneratorInfo = {
genDockerFile,
genDockerCompose,
genDockerComposeDebug
genDockerComposeDebug,
defaultPort: '3000'
};
function genDockerFile(serviceNameAndRelativePath: string, platform: string, os: string | undefined, port: string, { cmd, author, version, artifactName }: Partial<PackageInfo>): string {
let exposeStatements = getExposeStatements(port);
return `# Python support can be specified down to the minor or micro version
# (e.g. 3.6 or 3.6.3).
# OS Support also exists for jessie & stretch (slim and full).
@ -23,7 +26,7 @@ FROM python:alpine
#FROM continuumio/miniconda3
LABEL Name=${serviceNameAndRelativePath} Version=${version}
EXPOSE ${port}
${exposeStatements}
WORKDIR /app
ADD . /app

Просмотреть файл

@ -3,19 +3,22 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { PackageInfo } from './configure';
import { getExposeStatements, IPlatformGeneratorInfo, PackageInfo } from './configure';
export let configureRuby = {
export let configureRuby: IPlatformGeneratorInfo = {
genDockerFile,
genDockerCompose,
genDockerComposeDebug
genDockerComposeDebug,
defaultPort: '3000'
};
function genDockerFile(serviceNameAndRelativePath: string, platform: string, os: string | undefined, port: string, { cmd, author, version, artifactName }: Partial<PackageInfo>): string {
let exposeStatements = getExposeStatements(port);
return `FROM ruby:2.5-slim
LABEL Name=${serviceNameAndRelativePath} Version=${version}
EXPOSE ${port}
${exposeStatements}
# throw errors if Gemfile has been modified since Gemfile.lock
RUN bundle config --global frozen 1

Просмотреть файл

@ -11,7 +11,7 @@ import * as path from 'path';
import { Platform, OS } from "../configureWorkspace/config-utils";
import { ext } from '../extensionVariables';
import { Suite } from 'mocha';
import { configure, ConfigureTelemetryProperties, configureApi, ConfigureApiOptions } from '../configureWorkspace/configure';
import { configure, ConfigureTelemetryProperties, ConfigureApiOptions } from '../configureWorkspace/configure';
import { TestUserInput, IActionContext, TelemetryProperties } from 'vscode-azureextensionui';
import { globAsync } from '../helpers/async';
import { getTestRootFolder, constants, testInEmptyFolder } from './global.test';
@ -393,7 +393,7 @@ suite("Configure (Add Docker files to Workspace)", function (this: Suite): void
packageFileType: '.csproj',
packageFileSubfolderDepth: '1'
},
[os, '' /* no port */],
[os, undefined /* no port */],
['Dockerfile', '.dockerignore', `${projectFolder}/Program.cs`, `${projectFolder}/${projectFileName}`]
);
@ -599,6 +599,17 @@ suite("Configure (Add Docker files to Workspace)", function (this: Suite): void
});
suite(".NET Core Console 2.1", async () => {
testInEmptyFolder("Default port (none)", async () => {
await writeFile('projectFolder1', 'aspnetapp.csproj', dotNetCoreConsole_21_ProjectFileContents);
await testConfigureDocker(
'.NET Core Console',
undefined,
['Windows', undefined]
);
assertNotFileContains('Dockerfile', 'EXPOSE');
});
testInEmptyFolder("Windows", async () => {
await testDotNetCoreConsole(
'Windows',
@ -783,6 +794,28 @@ suite("Configure (Add Docker files to Workspace)", function (this: Suite): void
// ASP.NET Core
suite("ASP.NET Core 2.2", async () => {
testInEmptyFolder("Default port (80)", async () => {
await writeFile('projectFolder1', 'aspnetapp.csproj', dotNetCoreConsole_21_ProjectFileContents);
await testConfigureDocker(
'ASP.NET Core',
undefined,
['Windows', undefined]
);
assertFileContains('Dockerfile', 'EXPOSE 80');
});
testInEmptyFolder("No port", async () => {
await writeFile('projectFolder1', 'aspnetapp.csproj', dotNetCoreConsole_21_ProjectFileContents);
await testConfigureDocker(
'ASP.NET Core',
undefined,
['Windows', '']
);
assertNotFileContains('Dockerfile', 'EXPOSE');
});
testInEmptyFolder("Windows 10 RS4", async () => {
await testAspNetCore(
'Windows',
@ -922,6 +955,28 @@ suite("Configure (Add Docker files to Workspace)", function (this: Suite): void
// Java
suite("Java", () => {
testInEmptyFolder("No port", async () => {
await testConfigureDocker(
'Java',
undefined,
[''],
['Dockerfile', 'docker-compose.debug.yml', 'docker-compose.yml', '.dockerignore']
);
assertNotFileContains('Dockerfile', 'EXPOSE');
});
testInEmptyFolder("Default port", async () => {
await testConfigureDocker(
'Java',
undefined,
[undefined],
['Dockerfile', 'docker-compose.debug.yml', 'docker-compose.yml', '.dockerignore']
);
assertFileContains('Dockerfile', 'EXPOSE 3000');
});
testInEmptyFolder("No pom file", async () => {
await testConfigureDocker(
'Java',