Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add azd intellij plugin into azure-toolkit-for-intellij #9712

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dependencies {
intellijPlatform {
bundledPlugin("org.jetbrains.plugins.terminal")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.microsoft.azure.toolkit.intellij.azd.actions;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.terminal.ui.TerminalWidget;
import com.microsoft.azure.toolkit.intellij.azd.utils.AzdCliUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.terminal.TerminalToolWindowManager;

import java.util.Set;

public abstract class AzdCommandAction extends AnAction {

@Override
public void actionPerformed(@NotNull AnActionEvent event) {
Project project = event.getProject();
if (project == null) return;

VirtualFile file = event.getData(CommonDataKeys.VIRTUAL_FILE);
if (file == null) return;

String directory = file.getParent() != null ? file.getParent().getPath() : null;
if (directory == null) return;

String command = event.getPresentation().getDescription();
if (command == null || command.isEmpty()) return;

// Check if azd installed
if (AzdCliUtils.getAzdVersion() == null) {
// Prompt the user to install azd
int result = Messages.showYesNoDialog(
project,
"Azure Developer CLI is not installed. Would you like to install it?",
"Install Azure Developer CLI",
"Install",
"Later",
Messages.getQuestionIcon()
);
if (result == Messages.YES) {
if (!AzdCliUtils.installAzdCli(project)) {
Copy link
Author

@haoozhang haoozhang Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Install successfully first, then run azd commands in the terminal.

return;
}
} else {
return;
}
}

// Create new terminal tab under the `directory`
TerminalToolWindowManager terminalManager = TerminalToolWindowManager.getInstance(project);
TerminalWidget terminal = terminalManager.createShellWidget(directory, command, true, true);

// Run the command
terminal.sendCommandToExecute(command);
}

@Override
public void update(@NotNull AnActionEvent event) {
VirtualFile file = event.getData(CommonDataKeys.VIRTUAL_FILE);
// Only enable the action for specific files
boolean enabled = file != null && getSupportedFiles().contains(file.getName());
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We enable the azd related actions for only specific file patterns (such as pom.xml, azure.yaml). For other files or directories, the user should not see azd actions when clicking azure.

event.getPresentation().setEnabledAndVisible(enabled);
}

public abstract Set<String> getSupportedFiles();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.microsoft.azure.toolkit.intellij.azd.actions;

import java.util.Set;

public class AzdInitCommandAction extends AzdCommandAction {

@Override
public Set<String> getSupportedFiles() {
return Set.of("pom.xml");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.microsoft.azure.toolkit.intellij.azd.actions;

import java.util.Set;

public class AzdUpCommandAction extends AzdCommandAction {

@Override
public Set<String> getSupportedFiles() {
return Set.of("azure.yaml");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package com.microsoft.azure.toolkit.intellij.azd.utils;

import com.google.gson.Gson;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessListener;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.project.Project;

import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;

public class AzdCliUtils {

private static final Logger logger = Logger.getInstance(AzdCliUtils.class);

private static final long CACHE_LIFETIME = 15 * 60 * 1000; // 15 minutes

private static long lastCheckTime = 0;

private static AzdVersion cachedAzdVersion = null;

@Nullable
public static AzdVersion getAzdVersion() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is currently used to check if azd cli is installed locally.

// Check if the cached result is still valid
long currentTime = System.currentTimeMillis();
if (cachedAzdVersion != null && (currentTime - lastCheckTime) < CACHE_LIFETIME) {
return cachedAzdVersion;
}

GeneralCommandLine commandLine = new GeneralCommandLine()
.withExePath("azd")
.withParameters("version", "--output", "json");

try {
ProcessOutput output = runCommand(commandLine);

if (output.getExitCode() == 0) {
String stdout = output.getStdout();
Gson gson = new Gson();
AzdVersion azdVersion = gson.fromJson(stdout, AzdVersion.class);
// Cache the result
cachedAzdVersion = azdVersion;
lastCheckTime = currentTime;

return azdVersion;
} else {
logger.warn("Failed to check azd login status. Exit code: " + output.getExitCode());
}
} catch (Exception e) {
logger.warn("Unexpected error while checking azd login status", e);
}

return null;
}

public static class AzdVersion {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Introduce this model because of the output of the command azd version --output json.

image


private AzdVersionInfo azd;

public AzdVersionInfo getAzd() {
return azd;
}

public static class AzdVersionInfo {
private String version;
private String commit;

public String getVersion() {
return version;
}

public String getCommit() {
return commit;
}
}
}

public static boolean installAzdCli(@NotNull Project project) {
try {
GeneralCommandLine commandLine = getInstallationCommandLine();

ProcessOutput output = runCommand(commandLine);

if (output.getExitCode() == 0) {
Messages.showInfoMessage(project, "Azure Developer CLI (azd) installed successfully!", "Installation Complete");
return true;
} else {
Messages.showErrorDialog(project, "Failed to install Azure Developer CLI (azd). Error: " + output.getStderr(), "Installation Failed");
return false;
}
} catch (Exception e) {
Messages.showErrorDialog(project, "An error occurred while installing Azure Developer CLI (azd): " + e.getMessage(), "Installation Error");
}
return false;
}

private static ProcessOutput runCommand(GeneralCommandLine commandLine) throws ExecutionException {
OSProcessHandler processHandler = new OSProcessHandler(commandLine);
ProcessOutput output = new ProcessOutput();

processHandler.addProcessListener(new ProcessListener() {
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
if (ProcessOutputTypes.STDOUT.equals(outputType)) {
output.appendStdout(event.getText());
} else if (ProcessOutputTypes.STDERR.equals(outputType)) {
output.appendStderr(event.getText());
}
}

@Override
public void processTerminated(ProcessEvent event) {
output.setExitCode(event.getExitCode());
}
});

processHandler.startNotify();
processHandler.waitFor(TimeUnit.SECONDS.toMillis(30)); // Wait up to 30 seconds
return output;
}

private static GeneralCommandLine getInstallationCommandLine() {
GeneralCommandLine commandLine = new GeneralCommandLine();

// See https://aka.ms/azd-install
if (SystemInfo.isWindows) {
commandLine.withExePath("powershell")
.withParameters("-ex", "AllSigned")
.withParameters("-c", "Invoke-RestMethod 'https://aka.ms/install-azd.ps1' | Invoke-Expression");
} else if (SystemInfo.isLinux || SystemInfo.isMac) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same command for Linux and Mac platforms.

commandLine.withExePath("curl")
.withParameters("-fsSL", "https://aka.ms/install-azd.sh")
.withParameters("|", "bash");
} else {
String osName = System.getProperty("os.name");
logger.error("Unsupported platform: " + osName);
throw new UnsupportedOperationException("Unsupported platform: " + osName);
}

return commandLine;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<idea-plugin>
<depends>com.intellij.modules.platform</depends>
<depends>org.jetbrains.plugins.terminal</depends>

<actions>
<action id="Actions.AzdInit" class="com.microsoft.azure.toolkit.intellij.azd.actions.AzdInitCommandAction"
text="Generate Azure Deployment Script (azd init)" description="azd init"
icon="/icons/azd-logo.svg">
</action>
<action id="Actions.AzdUp" class="com.microsoft.azure.toolkit.intellij.azd.actions.AzdUpCommandAction"
text="Package, Provision, and Deploy to Azure (azd up)" description="azd up"
icon="/icons/azd-logo.svg">
</action>
</actions>
</idea-plugin>
Loading