Skip to content

Commit 1da4d49

Browse files
committed
Impl
1 parent 3e8ea38 commit 1da4d49

19 files changed

+306
-3
lines changed

extension.bundle.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export * from '@microsoft/vscode-azext-utils';
1818
// Export activate/deactivate for main.js
1919
export * from './src/commands/deployWorkspaceProject/DeployWorkspaceProjectContext';
2020
export * from './src/commands/deployWorkspaceProject/getDefaultValues/DefaultResourcesNameStep';
21+
export * from './src/commands/ingress/IngressContext';
22+
export * from './src/commands/ingress/editTargetPort/getDefaultPort';
23+
export * from './src/commands/ingress/tryConfigureIngressUsingDockerfile';
2124
export { activate, deactivate } from './src/extension';
2225
export * from './src/extensionVariables';
2326
export * from './src/utils/validateUtils';

gulpfile.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ async function cleanReadme(): Promise<void> {
2626
await fse.writeFile(readmePath, data);
2727
}
2828

29+
async function preTest(): Promise<void> {
30+
const fromPath: string = path.join(__dirname, 'test', 'ingress', 'dockerfileSamples');
31+
const toPath: string = path.join(__dirname, 'dist', 'test', 'ingress', 'dockerfileSamples');
32+
fse.copySync(fromPath, toPath);
33+
}
34+
2935
exports['webpack-dev'] = gulp.series(prepareForWebpack, () => gulp_webpack('development'));
3036
exports['webpack-prod'] = gulp.series(prepareForWebpack, () => gulp_webpack('production'));
3137
exports.cleanReadme = cleanReadme;
38+
exports.preTest = preTest;

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,8 @@
504504
"package": "vsce package --githubBranch main --no-dependencies",
505505
"lint": "eslint --ext .ts .",
506506
"lint-fix": "eslint --ext .ts . --fix",
507-
"test": "node ./out/test/runTest.js",
507+
"pretest": "gulp preTest",
508+
"test": "node ./dist/test/runTest.js",
508509
"webpack": "tsc && gulp webpack-dev",
509510
"webpack-profile": "webpack --profile --json --mode production > webpack-stats.json && echo Use http://webpack.github.io/analyse to analyze the stats",
510511
"prepare": "husky install"

src/commands/createContainerApp/createContainerApp.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export async function createContainerApp(context: IActionContext & Partial<ICrea
3030
...(await createActivityContext()),
3131
subscription: node.subscription,
3232
managedEnvironmentId: node.managedEnvironment.id,
33+
alwaysPromptIngress: true
3334
};
3435

3536
const title: string = localize('createContainerApp', 'Create Container App');

src/commands/deployImage/deployImage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export async function deployImage(context: ITreeItemPickerContext & Partial<IDep
2727
...context,
2828
...createSubscriptionContext(subscription),
2929
subscription,
30-
containerApp
30+
containerApp,
3131
};
3232

3333
const promptSteps: AzureWizardPromptStep<IDeployImageContext>[] = [

src/commands/ingress/IngressContext.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@
55

66
import type { ExecuteActivityContext } from "@microsoft/vscode-azext-utils";
77
import type { IContainerAppContext } from "../IContainerAppContext";
8+
import type { PortRange } from "./tryConfigureIngressUsingDockerfile";
89

910
export interface IngressContext extends IContainerAppContext, ExecuteActivityContext {
1011
enableIngress?: boolean;
1112
enableExternal?: boolean;
1213

1314
targetPort?: number;
15+
16+
// For detecting an expose port using a workspace Dockerfile
17+
dockerfilePath?: string;
18+
dockerfileExposePorts?: PortRange[];
19+
alwaysPromptIngress?: boolean;
1420
}

src/commands/ingress/IngressPromptStep.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ import { DisableIngressStep } from "./disableIngress/DisableIngressStep";
1010
import { TargetPortInputStep } from "./editTargetPort/TargetPortInputStep";
1111
import { EnableIngressStep } from "./enableIngress/EnableIngressStep";
1212
import { IngressVisibilityStep } from "./enableIngress/IngressVisibilityStep";
13+
import { tryConfigureIngressUsingDockerfile } from "./tryConfigureIngressUsingDockerfile";
1314

1415
export class IngressPromptStep extends AzureWizardPromptStep<IngressContext> {
1516
public async prompt(context: IngressContext): Promise<void> {
1617
context.enableIngress = (await context.ui.showQuickPick([{ label: localize('enable', 'Enable'), data: true }, { label: localize('disable', 'Disable'), data: false }],
1718
{ placeHolder: localize('enableIngress', 'Enable ingress for applications that need an HTTP endpoint.') })).data;
1819
}
1920

21+
public async configureBeforePrompt(context: IngressContext): Promise<void> {
22+
await tryConfigureIngressUsingDockerfile(context);
23+
}
24+
2025
public shouldPrompt(context: IngressContext): boolean {
2126
return context.enableIngress === undefined;
2227
}

src/commands/ingress/editTargetPort/getDefaultPort.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,15 @@
66
import type { IngressContext } from "../IngressContext";
77

88
export function getDefaultPort(context: IngressContext, fallbackPort: number = 80): number {
9-
return context.containerApp?.configuration?.ingress?.targetPort || fallbackPort;
9+
const currentDeploymentPort: number | undefined = context.containerApp?.configuration?.ingress?.targetPort;
10+
11+
// If the new deployment's Dockerfile port range doesn't include the current deployed target port, suggest a new one in the appropriate EXPOSE range
12+
let dockerfilePortSuggestion: number | undefined;
13+
if (currentDeploymentPort && context.dockerfileExposePorts && !context.dockerfileExposePorts.some(p => p.includes(currentDeploymentPort))) {
14+
dockerfilePortSuggestion = context.dockerfileExposePorts[0].start;
15+
} else if (!currentDeploymentPort && context.dockerfileExposePorts) {
16+
dockerfilePortSuggestion = context.dockerfileExposePorts[0].start;
17+
}
18+
19+
return dockerfilePortSuggestion || currentDeploymentPort || fallbackPort;
1020
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
7+
import { ext } from '../../extensionVariables';
8+
import { localize } from '../../utils/localize';
9+
import { IngressContext } from './IngressContext';
10+
import { getDefaultPort } from './editTargetPort/getDefaultPort';
11+
12+
export async function tryConfigureIngressUsingDockerfile(context: IngressContext): Promise<void> {
13+
if (!context.dockerfilePath) {
14+
return;
15+
}
16+
17+
context.dockerfileExposePorts = await getDockerfileExposePort(context.dockerfilePath);
18+
19+
if (context.alwaysPromptIngress) {
20+
return;
21+
}
22+
23+
if (!context.dockerfileExposePorts) {
24+
context.enableIngress = false;
25+
context.enableExternal = false;
26+
} else if (context.dockerfileExposePorts) {
27+
context.enableIngress = true;
28+
context.enableExternal = true;
29+
context.targetPort = getDefaultPort(context);
30+
}
31+
32+
// If a container app already exists, activity children will be added automatically in later execute steps
33+
// if (!context.containerApp) {
34+
// context.activityChildren?.push(
35+
// new GenericTreeItem(undefined, {
36+
// contextValue: createActivityChildContext(['ingressPromptStep', activitySuccessContext]),
37+
// label: context.enableIngress ?
38+
// localize('ingressEnableLabel', 'Enable ingress on port {0} (found Dockerfile configuration)', context.targetPort) :
39+
// localize('ingressDisableLabel', 'Disable ingress (found Dockerfile configuration)'),
40+
// iconPath: activitySuccessIcon
41+
// })
42+
// );
43+
// }
44+
45+
ext.outputChannel.appendLog(context.enableIngress ?
46+
localize('ingressEnabledLabel', 'Detected ingress on port {0} using Dockerfile configuration.', context.targetPort) :
47+
localize('ingressDisabledLabel', 'Detected no ingress using Dockerfile configuration.')
48+
);
49+
}
50+
51+
export class PortRange {
52+
private readonly _start: number;
53+
private readonly _end: number;
54+
55+
constructor(start: number, end?: number) {
56+
this._start = start;
57+
this._end = end ? end : start;
58+
}
59+
60+
get start(): number {
61+
return this._start;
62+
}
63+
64+
get end(): number {
65+
return this._end;
66+
}
67+
68+
includes(port: number): boolean {
69+
return port >= this.start && port <= this.end;
70+
}
71+
}
72+
73+
export async function getDockerfileExposePort(dockerfilePath: string): Promise<PortRange[] | undefined> {
74+
if (!await AzExtFsExtra.pathExists(dockerfilePath)) {
75+
return undefined;
76+
}
77+
78+
const content: string = await AzExtFsExtra.readFile(dockerfilePath);
79+
const lines: string[] = content.split('\n');
80+
81+
const portRanges: PortRange[] = [];
82+
for (const line of lines) {
83+
if (/^EXPOSE/i.test(line.trim())) {
84+
// Identify all single port numbers that aren't for udp
85+
const singlePorts: string[] = line.match(/(?<=\s)\d{2,5}(?!(\-)|(\/udp))\b/g) ?? [];
86+
for (const sp of singlePorts) {
87+
portRanges.push(new PortRange(parseInt(sp)));
88+
}
89+
90+
// Identify all port ranges
91+
const portRange: string[] = line.match(/\d{2,5}\-\d{2,5}/g) ?? [];
92+
for (const pr of portRange) {
93+
const [start, end] = pr.split('-');
94+
portRanges.push(new PortRange(parseInt(start), parseInt(end)));
95+
}
96+
}
97+
}
98+
99+
return portRanges.length ? portRanges : undefined;
100+
}

test/ingress/MockIngressContext.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { PortRange } from "../../extension.bundle";
7+
8+
export interface MockIngressContext {
9+
containerApp?: { configuration: { ingress: { targetPort: number } } };
10+
11+
enableIngress?: boolean;
12+
enableExternal?: boolean;
13+
14+
targetPort?: number;
15+
16+
dockerfilePath?: string;
17+
dockerfileExposePorts?: PortRange[];
18+
alwaysPromptIngress?: boolean;
19+
}

0 commit comments

Comments
 (0)