Skip to content

Commit

Permalink
Support "Local Deploy" in the VSCode Deployment Pane (#14234)
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-c-martin authored Jun 10, 2024
1 parent 4ee772f commit 67b96eb
Show file tree
Hide file tree
Showing 9 changed files with 437 additions and 57 deletions.
29 changes: 18 additions & 11 deletions src/Bicep.LangServer/Handlers/GetDeploymentDataHandler.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Bicep.Core.Semantics;
using Bicep.Core.Workspaces;
using Bicep.LanguageServer.CompilationManager;
using MediatR;
Expand All @@ -14,7 +15,7 @@ namespace Bicep.LanguageServer.Handlers
public record GetDeploymentDataRequest(TextDocumentIdentifier TextDocument)
: ITextDocumentIdentifierParams, IRequest<GetDeploymentDataResponse>;

public record GetDeploymentDataResponse(string? TemplateJson = null, string? ParametersJson = null, string? ErrorMessage = null);
public record GetDeploymentDataResponse(bool LocalDeployEnabled, string? TemplateJson = null, string? ParametersJson = null, string? ErrorMessage = null);

public class GetDeploymentDataHandler : IJsonRpcRequestHandler<GetDeploymentDataRequest, GetDeploymentDataResponse>
{
Expand All @@ -33,32 +34,38 @@ public async Task<GetDeploymentDataResponse> Handle(GetDeploymentDataRequest req

if (this.compilationManager.GetCompilation(request.TextDocument.Uri) is not { } context)
{
return new(ErrorMessage: $"Bicep compilation failed. An unexpected error occurred.");
return new(ErrorMessage: $"Bicep compilation failed. An unexpected error occurred.", LocalDeployEnabled: false);
}

var semanticModel = context.Compilation.GetEntrypointSemanticModel();
var localDeployEnabled = semanticModel.Features.LocalDeployEnabled;

string? paramsFile = null;
string? templateFile = null;
if (semanticModel.Root.FileKind == BicepSourceFileKind.ParamsFile)
{
var result = context.Compilation.Emitter.Parameters();

if (result.Parameters is null ||
result.Template?.Template is null)
{
return new(ErrorMessage: $"Bicep compilation failed. The Bicep parameters file contains errors.");
return new(ErrorMessage: $"Bicep compilation failed. The Bicep parameters file contains errors.", LocalDeployEnabled: localDeployEnabled);
}

return new(TemplateJson: result.Template.Template, ParametersJson: result.Parameters);
paramsFile = result.Parameters;
templateFile = result.Template.Template;

if (!semanticModel.Root.TryGetBicepFileSemanticModelViaUsing().IsSuccess(out var usingModel))
{
return new(ErrorMessage: $"Bicep compilation failed. The Bicep parameters file contains errors.", LocalDeployEnabled: localDeployEnabled);
}
}
else
{
var result = context.Compilation.Emitter.Template();
if (result.Template is null)
{
return new(ErrorMessage: $"Bicep compilation failed. The Bicep file contains errors.");
}

return new(TemplateJson: result.Template);
return new(ErrorMessage: $"Bicep compilation failed. The Bicep file contains errors.", LocalDeployEnabled: localDeployEnabled);
}

return new(TemplateJson: templateFile, ParametersJson: paramsFile, LocalDeployEnabled: localDeployEnabled);
}
}
}
128 changes: 128 additions & 0 deletions src/Bicep.LangServer/Handlers/LocalDeployHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Immutable;
using Bicep.Local.Deploy;
using Bicep.Local.Deploy.Extensibility;
using Azure.Deployments.Core.Definitions;
using Azure.Deployments.Core.ErrorResponses;
using Bicep.Core.Extensions;
using Bicep.Core.Registry;
using Bicep.Core.Semantics;
using Bicep.Core.TypeSystem.Types;
using Bicep.LanguageServer.CompilationManager;
using MediatR;
using Microsoft.WindowsAzure.ResourceStack.Common.Json;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using OmniSharp.Extensions.LanguageServer.Protocol.Window;

namespace Bicep.LanguageServer.Handlers;

[Method("bicep/localDeploy", Direction.ClientToServer)]
public record LocalDeployRequest(TextDocumentIdentifier TextDocument)
: ITextDocumentIdentifierParams, IRequest<LocalDeployResponse>;

public record LocalDeploymentContent(
string ProvisioningState,
ImmutableDictionary<string, JToken> Outputs,
LocalDeploymentOperationError? Error);

public record LocalDeploymentOperationError(
string Code,
string Message,
string Target);

public record LocalDeploymentOperationContent(
string ResourceName,
string ProvisioningState,
LocalDeploymentOperationError? Error);

public record LocalDeployResponse(
LocalDeploymentContent Deployment,
ImmutableArray<LocalDeploymentOperationContent> Operations);

public class LocalDeployHandler : IJsonRpcRequestHandler<LocalDeployRequest, LocalDeployResponse>
{
private readonly IModuleDispatcher moduleDispatcher;
private readonly ICompilationManager compilationManager;
private readonly ILanguageServerFacade server;

public LocalDeployHandler(IModuleDispatcher moduleDispatcher, ICompilationManager compilationManager, ILanguageServerFacade server)
{
this.moduleDispatcher = moduleDispatcher;
this.compilationManager = compilationManager;
this.server = server;
}

public async Task<LocalDeployResponse> Handle(LocalDeployRequest request, CancellationToken cancellationToken)
{
try
{
if (this.compilationManager.GetCompilation(request.TextDocument.Uri) is not { } context)
{
throw new InvalidOperationException("Failed to find active compilation.");
}

var paramsModel = context.Compilation.GetEntrypointSemanticModel();
//Failure scenario is ignored since a diagnostic for it would be emitted during semantic analysis
if (paramsModel.HasErrors() ||
!paramsModel.Root.TryGetBicepFileSemanticModelViaUsing().IsSuccess(out var usingModel))
{
throw new InvalidOperationException("Bicep file had errors.");
}

var parameters = context.Compilation.Emitter.Parameters();
if (parameters.Parameters is not {} parametersString ||
parameters.Template?.Template is not {} templateString)
{
throw new InvalidOperationException("Bicep file had errors.");
}

await using LocalExtensibilityHandler extensibilityHandler = new(moduleDispatcher, GrpcExtensibilityProvider.Start);
await extensibilityHandler.InitializeProviders(context.Compilation);

var result = await LocalDeployment.Deploy(extensibilityHandler, templateString, parametersString, cancellationToken);

return FromResult(result);
}
catch (Exception ex)
{
server.Window.LogError($"Unhandled exception during local deployment: {ex}");
return new(
new("Failed", ImmutableDictionary<string, JToken>.Empty, new("UnhandledException", ex.Message, "")),
ImmutableArray<LocalDeploymentOperationContent>.Empty
);
}
}

private static LocalDeploymentOperationContent FromOperation(DeploymentOperationDefinition operation)
{
var result = operation.Properties.StatusMessage.TryFromJToken<OperationResult>();
var error = result?.Error?.Message.TryFromJson<ErrorResponseMessage>()?.Error;
var operationError = error is {} ? new LocalDeploymentOperationError(error.Code, error.Message, error.Target) : null;

return new LocalDeploymentOperationContent(
operation.Properties.TargetResource.SymbolicName,
operation.Properties.ProvisioningState.ToString(),
operationError);
}

private static LocalDeployResponse FromResult(LocalDeployment.Result result)
{
var deployError = result.Deployment.Properties.Error is {} error ?
new LocalDeploymentOperationError(error.Code, error.Message, error.Target) : null;


LocalDeploymentContent deployment = new(
result.Deployment.Properties.ProvisioningState.ToString() ?? "Failed",
result.Deployment.Properties.Outputs?.ToImmutableDictionary(x => x.Key, x => x.Value.Value) ?? ImmutableDictionary<string, JToken>.Empty,
deployError);

var operations = result.Operations.Select(FromOperation).ToImmutableArray();

return new(deployment, operations);
}
}
1 change: 1 addition & 0 deletions src/Bicep.LangServer/Server.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public Server(Action<LanguageServerOptions> onOptionsFunc)
.WithHandler<BicepExternalSourceRequestHandler>()
.WithHandler<InsertResourceHandler>()
.WithHandler<ConfigurationSettingsHandler>()
.WithHandler<LocalDeployHandler>()
.WithServices(RegisterServices);

onOptionsFunc(options);
Expand Down
36 changes: 36 additions & 0 deletions src/vscode-bicep/src/language/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface GetDeploymentDataRequest {
}

export interface GetDeploymentDataResponse {
localDeployEnabled: boolean;
templateJson?: string;
parametersJson?: string;
errorMessage?: string;
Expand All @@ -59,6 +60,41 @@ export const getDeploymentDataRequestType = new ProtocolRequestType<
void
>("bicep/getDeploymentData");

export interface LocalDeployRequest {
textDocument: TextDocumentIdentifier;
}

export interface LocalDeploymentOperationError {
code: string;
message: string;
target: string;
}

export interface LocalDeploymentOperationContent {
resourceName: string;
provisioningState: string;
error?: LocalDeploymentOperationError;
}

interface LocalDeploymentContent {
provisioningState: string;
outputs: Record<string, unknown>;
error?: LocalDeploymentOperationError;
}

export interface LocalDeployResponse {
deployment: LocalDeploymentContent;
operations: LocalDeploymentOperationContent[];
}

export const localDeployRequestType = new ProtocolRequestType<
LocalDeployRequest,
LocalDeployResponse,
never,
void,
void
>("bicep/localDeploy");

export interface BicepDeploymentScopeParams {
textDocument: TextDocumentIdentifier;
}
Expand Down
Loading

0 comments on commit 67b96eb

Please sign in to comment.