Skip to content

Commit 70424c2

Browse files
authored
Merge pull request #1001 from JetBrains/ilnarag/appservice-debugging
Implementing Remote Debugging for Azure App Services
2 parents 955fee2 + 0264d87 commit 70424c2

38 files changed

+1462
-13
lines changed

PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-lib/src/main/java/com/microsoft/azure/toolkit/intellij/common/action/IntellijAzureActionManager.java

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ public void update(@Nonnull AnActionEvent e) {
235235

236236
private void addActions(List<Object> actions) {
237237
for (final Object raw : actions) {
238-
doAddAction(raw);
238+
doAddAction(raw, Constraints.LAST);
239239
}
240240
}
241241

@@ -258,44 +258,54 @@ public List<Object> getActions() {
258258
return group.getActions();
259259
}
260260

261+
// TODO: Move changes to the base plugin (https://github.com/microsoft/azure-tools-for-java/pull/9706)
261262
@Override
262263
public void addAction(Object raw) {
263264
this.group.addAction(raw);
264-
this.doAddAction(raw);
265+
doAddAction(raw, Constraints.LAST);
265266
}
266267

267268
@Override
268269
public void prependAction(Object action) {
269-
throw new NotImplementedException();
270+
this.group.prependAction(action);
271+
doAddAction(action, Constraints.FIRST);
270272
}
271273

272-
public void doAddAction(Object raw) {
274+
private void doAddAction(Object raw, Constraints constraints) {
275+
AnAction anAction = getAction(raw);
276+
if (anAction != null) {
277+
this.add(anAction, constraints);
278+
}
279+
}
280+
281+
private AnAction getAction(Object raw) {
273282
if (raw instanceof Action.Id) {
274283
raw = ((Action.Id<?>) raw).getId();
275284
}
276-
if (raw instanceof String) {
277-
final String actionId = (String) raw;
285+
if (raw instanceof String actionId) {
278286
if (actionId.startsWith("-")) {
279287
final String title = actionId.replaceAll("-", "").trim();
280288
if (StringUtils.isBlank(title)) {
281-
this.addSeparator();
289+
return Separator.create();
282290
} else {
283-
this.addSeparator(title);
291+
return Separator.create(title);
284292
}
285293
} else if (StringUtils.isNotBlank(actionId)) {
286294
final ActionManager am = ActionManager.getInstance();
287295
final AnAction action = am.getAction(actionId);
288296
if (action instanceof com.intellij.openapi.actionSystem.ActionGroup) {
289-
this.add(action);
297+
return action;
290298
} else if (Objects.nonNull(action)) {
291-
this.add(new com.intellij.openapi.actionSystem.AnActionWrapper(action));
299+
return new com.intellij.openapi.actionSystem.AnActionWrapper(action);
292300
}
293301
}
294302
} else if (raw instanceof Action<?>) {
295-
this.add(new AnActionWrapper<>((Action<?>) raw));
303+
return new AnActionWrapper<>((Action<?>) raw);
296304
} else if (raw instanceof ActionGroup) {
297-
this.add(new ActionGroupWrapper((ActionGroup) raw));
305+
return new ActionGroupWrapper((ActionGroup) raw);
298306
}
307+
308+
return null;
299309
}
300310

301311
public void registerCustomShortcutSetForActions(JComponent component, @Nullable Disposable disposable) {

PluginsAndFeatures/azure-toolkit-for-rider/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Azure Toolkit for Rider Changelog
44

55
## [Unreleased]
6+
- Support for debugging Azure App Services remotely
67

78
### Added
89

PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
alias(libs.plugins.kotlin)
33
alias(libs.plugins.serialization)
44
id("org.jetbrains.intellij.platform.module")
5+
id("java")
56
}
67

78
repositories {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license.
3+
*/
4+
5+
package com.microsoft.azure.toolkit.intellij;
6+
7+
import com.azure.core.annotation.*;
8+
import com.azure.core.http.rest.Response;
9+
import com.azure.core.http.rest.RestProxy;
10+
import com.azure.resourcemanager.appservice.models.WebAppBase;
11+
import com.microsoft.azure.toolkit.lib.appservice.AppServiceAppBase;
12+
import com.microsoft.azure.toolkit.lib.common.exception.AzureToolkitRuntimeException;
13+
import reactor.core.publisher.Mono;
14+
15+
import javax.annotation.Nonnull;
16+
import java.util.Locale;
17+
import java.util.Objects;
18+
19+
// TODO: Move it to the base plugin (https://github.com/microsoft/azure-maven-plugins/pull/2511)
20+
public class AppServiceKuduClientExt
21+
{
22+
private final String host;
23+
private final KuduServiceExt kuduService;
24+
private final AppServiceAppBase<?, ?, ?> app;
25+
26+
private AppServiceKuduClientExt(String host, KuduServiceExt kuduService, AppServiceAppBase<?, ?, ?> app) {
27+
this.host = host;
28+
this.app = app;
29+
this.kuduService = kuduService;
30+
}
31+
32+
public static AppServiceKuduClientExt getClient(@Nonnull WebAppBase webAppBase, @Nonnull AppServiceAppBase<?, ?, ?> appService) {
33+
// refers : https://github.com/Azure/azure-sdk-for-java/blob/master/sdk/resourcemanager/azure-resourcemanager-appservice/src/main/java/
34+
// com/azure/resourcemanager/appservice/implementation/KuduClient.java
35+
if (webAppBase.defaultHostname() == null) {
36+
throw new AzureToolkitRuntimeException("Cannot initialize kudu client before web app is created");
37+
}
38+
String host = webAppBase.defaultHostname().toLowerCase(Locale.ROOT)
39+
.replace("http://", "")
40+
.replace("https://", "");
41+
String[] parts = host.split("\\.", 2);
42+
host = parts[0] + ".scm." + parts[1];
43+
host = "https://" + host;
44+
45+
final KuduServiceExt kuduService = RestProxy.create(KuduServiceExt.class, webAppBase.manager().httpPipeline());
46+
return new AppServiceKuduClientExt(host, kuduService, appService);
47+
}
48+
49+
public ExtensionInfo getPackageFromRemoteStore(final @Nonnull String id) {
50+
var response = Objects.requireNonNull(this.kuduService.getPackageFromRemoteStore(host, id).block());
51+
if (response.getStatusCode() == 404) return null;
52+
53+
return response.getValue();
54+
}
55+
56+
public ExtensionInfo getInstalledPackage(final @Nonnull String id) {
57+
var response = Objects.requireNonNull(this.kuduService.getInstalledPackage(host, id).block());
58+
if (response.getStatusCode() == 404) return null;
59+
60+
return response.getValue();
61+
}
62+
63+
public ExtensionInfo installOrUpdatePackage(final @Nonnull String id) {
64+
return Objects.requireNonNull(this.kuduService.installOrUpdatePackage(host, id).block()).getValue();
65+
}
66+
67+
public void killKuduProcess() {
68+
killProcess(0);
69+
}
70+
71+
public void killProcess(final int id) {
72+
this.kuduService.killProcess(host, id).block();
73+
}
74+
75+
@Host("{$host}")
76+
@ServiceInterface(name = "KuduServiceExt")
77+
private interface KuduServiceExt {
78+
@Headers("Content-Type: application/json; charset=utf-8")
79+
@Get("/api/extensionfeed/{id}")
80+
@ExpectedResponses({200, 404})
81+
Mono<Response<ExtensionInfo>> getPackageFromRemoteStore(@HostParam("$host") String host, @PathParam("id") String id);
82+
83+
@Headers("Content-Type: application/json; charset=utf-8")
84+
@Get("/api/siteextensions/{id}")
85+
@ExpectedResponses({200, 404})
86+
Mono<Response<ExtensionInfo>> getInstalledPackage(@HostParam("$host") String host, @PathParam("id") String id);
87+
88+
@Headers("Content-Type: application/json; charset=utf-8")
89+
@Put("/api/siteextensions/{id}")
90+
Mono<Response<ExtensionInfo>> installOrUpdatePackage(@HostParam("$host") String host, @PathParam("id") String id);
91+
92+
@Delete("/api/processes/{id}")
93+
@ExpectedResponses({502})
94+
Mono<Void> killProcess(@HostParam("$host") String host, @PathParam("id") int id);
95+
}
96+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license.
3+
*/
4+
5+
package com.microsoft.azure.toolkit.intellij;
6+
7+
import lombok.Data;
8+
9+
import java.util.List;
10+
11+
@Data
12+
public class ExtensionInfo {
13+
public String id;
14+
public String title;
15+
public String type;
16+
public String summary;
17+
public String description;
18+
public String version;
19+
public String extensionUrl;
20+
public String projectUrl;
21+
public String iconUrl;
22+
public String licenseUrl;
23+
public String feedUrl;
24+
public List<String> authors;
25+
public String installerCommandLineParams;
26+
public String publishedDateTime;
27+
public int downloadCount;
28+
public boolean localIsLatestVersion;
29+
public String localPath;
30+
public String installedDateTime;
31+
public String provisioningState;
32+
public String comment;
33+
public String packageUri;
34+
}
35+

PluginsAndFeatures/azure-toolkit-for-rider/azure-intellij-plugin-appservice-dotnet/src/main/kotlin/com/microsoft/azure/toolkit/intellij/appservice/AppServiceRiderActionsContributor.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@ package com.microsoft.azure.toolkit.intellij.appservice
77
import com.intellij.openapi.actionSystem.AnActionEvent
88
import com.microsoft.azure.toolkit.ide.appservice.AppServiceActionsContributor
99
import com.microsoft.azure.toolkit.ide.appservice.file.AppServiceFileActionsContributor
10+
import com.microsoft.azure.toolkit.ide.appservice.function.FunctionAppActionsContributor.FUNCTION_APP_ACTIONS
11+
import com.microsoft.azure.toolkit.ide.appservice.webapp.WebAppActionsContributor.WEBAPP_ACTIONS
1012
import com.microsoft.azure.toolkit.ide.common.IActionsContributor
1113
import com.microsoft.azure.toolkit.ide.common.action.ResourceCommonActionsContributor
14+
import com.microsoft.azure.toolkit.ide.common.icon.AzureIcons
1215
import com.microsoft.azure.toolkit.ide.containerregistry.ContainerRegistryActionsContributor
1316
import com.microsoft.azure.toolkit.intellij.appservice.actions.AppServiceFileAction
17+
import com.microsoft.azure.toolkit.intellij.appservice.actions.AppServiceRemoteDebuggingHandler
1418
import com.microsoft.azure.toolkit.intellij.appservice.actions.OpenAppServicePropertyViewAction
19+
import com.microsoft.azure.toolkit.intellij.debugger.canBeDebugged
1520
import com.microsoft.azure.toolkit.intellij.legacy.function.actions.CreateFunctionAppAction
1621
import com.microsoft.azure.toolkit.intellij.legacy.function.actions.DeployFunctionAppAction
1722
import com.microsoft.azure.toolkit.intellij.legacy.webapp.action.CreateWebAppAction
1823
import com.microsoft.azure.toolkit.intellij.legacy.webapp.action.DeployWebAppAction
24+
import com.microsoft.azure.toolkit.lib.appservice.AppServiceAppBase
1925
import com.microsoft.azure.toolkit.lib.appservice.function.AzureFunctions
2026
import com.microsoft.azure.toolkit.lib.appservice.function.FunctionApp
2127
import com.microsoft.azure.toolkit.lib.appservice.function.FunctionAppDeploymentSlot
@@ -33,8 +39,15 @@ class AppServiceRiderActionsContributor : IActionsContributor {
3339
private val initializeOrder =
3440
max(AppServiceActionsContributor.INITIALIZE_ORDER, ContainerRegistryActionsContributor.INITIALIZE_ORDER) + 1
3541

42+
val REMOTE_DEBUGGING: Action.Id<AppServiceAppBase<*, *, *>> = Action.Id.of("user/appservice.start_remote_debugging.app")
43+
3644
override fun getOrder() = initializeOrder
3745

46+
override fun registerGroups(am: AzureActionManager) {
47+
am.getGroup(FUNCTION_APP_ACTIONS).prependActions(REMOTE_DEBUGGING, "---")
48+
am.getGroup(WEBAPP_ACTIONS).prependActions(REMOTE_DEBUGGING, "---")
49+
}
50+
3851
override fun registerActions(am: AzureActionManager) {
3952
val tm = AzureTaskManager.getInstance()
4053

@@ -62,6 +75,14 @@ class AppServiceRiderActionsContributor : IActionsContributor {
6275
}
6376
}
6477
.register(am)
78+
79+
Action(REMOTE_DEBUGGING)
80+
.withLabel("Attach Debugger")
81+
.withIcon(AzureIcons.Action.ATTACH_DEBUGGER.iconPath)
82+
.withIdParam { appService: AppServiceAppBase<*, *, *> -> appService.name }
83+
.visibleWhen { s: Any? -> s is AppServiceAppBase<*, *, *> }
84+
.enableWhen { appService: AppServiceAppBase<*, *, *> -> appService.canBeDebugged() }
85+
.register(am)
6586
}
6687

6788
override fun registerHandlers(am: AzureActionManager) {
@@ -97,13 +118,19 @@ class AppServiceRiderActionsContributor : IActionsContributor {
97118
ResourceCommonActionsContributor.SHOW_PROPERTIES,
98119
{ r, _ -> r is FunctionApp },
99120
{ c, e: AnActionEvent ->
100-
e.project?.let { OpenAppServicePropertyViewAction.openFunctionAppPropertyView(c as FunctionApp, it) }
121+
e.project?.let { OpenAppServicePropertyViewAction.openFunctionAppPropertyView(c as FunctionApp, it) }
101122
})
102123
am.registerHandler(
103124
ResourceCommonActionsContributor.SHOW_PROPERTIES,
104125
{ r, _ -> r is FunctionAppDeploymentSlot },
105126
{ c, e: AnActionEvent ->
106127
e.project?.let { OpenAppServicePropertyViewAction.openFunctionAppDeploymentSlotPropertyView(c as FunctionAppDeploymentSlot, it) }
107128
})
129+
130+
am.registerHandler(
131+
REMOTE_DEBUGGING,
132+
{ r, _ -> r is AppServiceAppBase<*, *, *> },
133+
AppServiceRemoteDebuggingHandler()
134+
)
108135
}
109136
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license.
3+
*/
4+
5+
package com.microsoft.azure.toolkit.intellij.appservice.actions
6+
7+
import com.intellij.openapi.actionSystem.CustomizedDataContext
8+
import com.intellij.openapi.actionSystem.DataContext
9+
import com.intellij.openapi.components.service
10+
import com.intellij.openapi.project.Project
11+
import com.intellij.xdebugger.attach.XAttachDebuggerProvider
12+
import com.intellij.xdebugger.attach.XAttachHost
13+
import com.intellij.xdebugger.attach.XAttachHostProvider
14+
import com.intellij.xdebugger.impl.ui.attach.dialog.AttachToProcessDialogFactory
15+
import com.intellij.xdebugger.impl.ui.attach.dialog.AttachToProcessViewWithHosts.Companion.DEFAULT_ATTACH_HOST
16+
import com.microsoft.azure.toolkit.intellij.debugger.AzureAttachDialogHostType
17+
import com.microsoft.azure.toolkit.lib.appservice.AppServiceAppBase
18+
19+
object AppServiceRemoteDebuggingAction {
20+
fun startDebugging(appService: AppServiceAppBase<*, *, *>, project: Project?, dataContext: DataContext) {
21+
val factory = project?.service<AttachToProcessDialogFactory>() ?: return
22+
23+
factory.showDialog(
24+
XAttachDebuggerProvider.EP.extensionList,
25+
XAttachHostProvider.EP.extensionList.filterIsInstance<XAttachHostProvider<XAttachHost>>(),
26+
CustomizedDataContext.withSnapshot(dataContext) { sink ->
27+
sink[AttachToProcessDialogFactory.DEFAULT_VIEW_HOST_TYPE] = AzureAttachDialogHostType
28+
sink[DEFAULT_ATTACH_HOST] = appService.id
29+
}
30+
)
31+
}
32+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2018-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the MIT license.
3+
*/
4+
5+
package com.microsoft.azure.toolkit.intellij.appservice.actions
6+
7+
import com.intellij.openapi.actionSystem.AnActionEvent
8+
import com.microsoft.azure.toolkit.lib.appservice.AppServiceAppBase
9+
import com.microsoft.azure.toolkit.lib.appservice.function.FunctionAppBase
10+
import com.microsoft.azure.toolkit.lib.common.task.AzureTaskManager
11+
import java.util.function.BiConsumer
12+
13+
class AppServiceRemoteDebuggingHandler : BiConsumer<AppServiceAppBase<*, *, *>, AnActionEvent> {
14+
override fun accept(function: AppServiceAppBase<*, *, *>, event: AnActionEvent) {
15+
AzureTaskManager.getInstance().runLater {
16+
AppServiceRemoteDebuggingAction.startDebugging(function, event.project, event.dataContext)
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)