Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions Microsoft.Mcp.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
<Project Path="tools/Azure.Mcp.Tools.AzureMigrate/src/Azure.Mcp.Tools.AzureMigrate.csproj" />
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.AzureMigrate/tests/">
<Project Path="tools/Azure.Mcp.Tools.AzureMigrate/tests/Azure.Mcp.Tools.AzureMigrate.LiveTests/Azure.Mcp.Tools.AzureMigrate.LiveTests.csproj" />
<Project Path="tools/Azure.Mcp.Tools.AzureMigrate/tests/Azure.Mcp.Tools.AzureMigrate.UnitTests/Azure.Mcp.Tools.AzureMigrate.UnitTests.csproj" />
</Folder>
<Folder Name="/tools/Azure.Mcp.Tools.AzureTerraformBestPractices/" />
Expand Down
1,236 changes: 1,236 additions & 0 deletions mcp.sln

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changes:
- section: "Features Added"
description: "Add createmigrateproject action in azmcp_azuremigrate_platformlandingzone_request command to create a new Azure Migrate project if one doesn't exist"
10 changes: 10 additions & 0 deletions servers/Azure.Mcp.Server/docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,16 @@ azmcp azuremigrate platformlandingzone request --subscription <subscription> \
--action status
```

6. **Create Azure Migrate Project** (`--action createmigrateproject`)
```bash
# Create a new Azure Migrate project if one doesn't exist (requires location parameter)
azmcp azuremigrate platformlandingzone request --subscription <subscription> \
--resource-group <resource-group> \
--migrate-project-name <migrate-project-name> \
--action createmigrateproject \
--location <azure-region>
```

### Azure Native ISV Operations

```bash
Expand Down
2 changes: 2 additions & 0 deletions servers/Azure.Mcp.Server/docs/e2eTestPrompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,8 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
| azuremigrate_platformlandingzone_request | Set up a single region landing zone with Azure Firewall for migrate project <migrate-project-name> |
| azuremigrate_platformlandingzone_request | Configure a multi-region landing zone with hub-spoke architecture for migrate project <migrate-project-name> in resource group <resource-group-name> |
| azuremigrate_platformlandingzone_request | Generate a platform landing zone for migrate project <migrate-project-name> in resource group <resource-group-name> |
| azuremigrate_platformlandingzone_request | Generate a platform landing zone
| azuremigrate_platformlandingzone_request | Generate a platform landing zone and create a new migrate project with name <migrate-project-name> in resource group <resource-group-name> |
| azuremigrate_platformlandingzone_request | Start landing zone generation for migrate project <migrate-project-name> |
| azuremigrate_platformlandingzone_request | Download the generated landing zone for migrate project <migrate-project-name> in resource group <resource-group-name> |
| azuremigrate_platformlandingzone_request | Check parameter status for migrate project <migrate-project-name> in resource group <resource-group-name> |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public void ConfigureServices(IServiceCollection services)
{
// Register shared helpers
services.AddSingleton<AzureHttpHelper>();
services.AddSingleton<AzureMigrateProjectHelper>();

// Register guidance service and command
services.AddSingleton<IPlatformLandingZoneGuidanceService, PlatformLandingZoneGuidanceService>();
Expand All @@ -42,7 +43,9 @@ public CommandGroup RegisterCommands(IServiceProvider serviceProvider)
{
var azureMigrate = new CommandGroup(
Name,
"Azure Landing Zone operations – Guidance and tooling for customizing and generating Azure Landing Zones, including policy configuration, networking, identity, governance, and naming standards. Supports generating platform landing zones using Bicep, Terraform, or the Azure portal in alignment with Microsoft's Cloud Adoption Framework.",
"""
Azure Landing Zone operations - Provides best-practice guidance and Terraform-ready generation for Azure Platform Landing Zones. Supports policy and governance changes, naming standards, network topology (hub/spoke/vWAN), identity and subscription design, firewall patterns, and starter module customization—aligned to Microsoft's Cloud Adoption Framework. Can generate a complete platform landing zone with configurable parameters (regions, connectivity, security, subscriptions).
""",
Title);

// Create platform landing zone subgroup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Azure.Mcp.Core.Commands.Subscription;
using Azure.Mcp.Core.Extensions;
using Azure.Mcp.Core.Models.Option;
using Azure.Mcp.Tools.AzureMigrate.Helpers;
using Azure.Mcp.Tools.AzureMigrate.Models;
using Azure.Mcp.Tools.AzureMigrate.Options.PlatformLandingZone;
using Azure.Mcp.Tools.AzureMigrate.Services;
Expand Down Expand Up @@ -39,15 +40,19 @@ public sealed class RequestCommand(ILogger<RequestCommand> logger)
Updates parameters, check existing landing zones, and view parameters status.

**Actions:**
- createmigrateproject: Create a new Azure Migrate project if one doesn't exist (requires location parameter)
- check: Check if a platform landing zone already exists
- update: Update all parameters for generation (collect ALL params in one call)
- generate: Generate the platform landing zone
- download: Download generated files to local workspace
- status: View cached parameters

**Context (required for all actions):**
**Context (required for most actions):**
- subscription, resourceGroup, migrateProjectName

**Create Azure Migrate Parameters (for 'createmigrateproject' action):**
- subscription, resourceGroup, migrateProjectName, location

**Generation Parameters (for 'update' action - collect ALL at once from user):**
| Parameter | Options | Default |
|-----------|---------|----------|
Expand All @@ -63,11 +68,13 @@ public sealed class RequestCommand(ILogger<RequestCommand> logger)
| connectivitySubscriptionId | GUID | (uses main subscription) |

**Workflow:**
1. action='check' - See if one already exists
2. action='update' with ALL parameters - Ask user to confirm defaults or provide values
3. action='generate' - Create the landing zone
4. action='download' - Get the files
5. Extract zip to workspace root
1. Ask the user if they want to create a new Azure Migrate project or use an existing one. If creating, collect location parameter and create the project.
2. action='createmigrateproject' - Create a new Azure Migrate project only if the user doesn't have one already. Requires location parameter.
3. action='check' - See if one already exists
4. action='update' with ALL parameters - Ask user to confirm defaults or provide values
5. action='generate' - Create the landing zone
6. action='download' - Get the files
7. Extract zip to workspace root

**IMPORTANT:** When using 'update', collect ALL parameters from the user in ONE call.
Show them the defaults and ask which ones they want to change.
Expand Down Expand Up @@ -103,6 +110,7 @@ protected override void RegisterOptions(Command command)
command.Options.Add(PlatformLandingZoneOptionDefinitions.OrganizationName);
command.Options.Add(PlatformLandingZoneOptionDefinitions.MigrateProjectName);
command.Options.Add(PlatformLandingZoneOptionDefinitions.MigrateProjectResourceId);
command.Options.Add(PlatformLandingZoneOptionDefinitions.Location);
}

/// <inheritdoc/>
Expand All @@ -124,6 +132,7 @@ protected override RequestOptions BindOptions(ParseResult parseResult)
options.OrganizationName = parseResult.GetValueOrDefault<string>(PlatformLandingZoneOptionDefinitions.OrganizationName.Name);
options.MigrateProjectName = parseResult.GetValueOrDefault<string>(PlatformLandingZoneOptionDefinitions.MigrateProjectName.Name)!;
options.MigrateProjectResourceId = parseResult.GetValueOrDefault<string>(PlatformLandingZoneOptionDefinitions.MigrateProjectResourceId.Name);
options.Location = parseResult.GetValueOrDefault<string>(PlatformLandingZoneOptionDefinitions.Location.Name);
return options;
}

Expand Down Expand Up @@ -168,12 +177,13 @@ public override async Task<CommandResponse> ExecuteAsync(

var result = action switch
{
"createmigrateproject" => await HandleCreateMigrateProjectActionAsync(context, options, cancellationToken),
"update" => await HandleUpdateActionAsync(platformLandingZoneService, landingZoneContext, options, cancellationToken),
"check" => await HandleCheckActionAsync(platformLandingZoneService, landingZoneContext, cancellationToken),
"generate" => await HandleGenerateActionAsync(platformLandingZoneService, landingZoneContext, cancellationToken),
"download" => await HandleDownloadActionAsync(platformLandingZoneService, landingZoneContext, cancellationToken),
"status" => HandleStatusAction(platformLandingZoneService, landingZoneContext),
_ => throw new ArgumentException($"Invalid action '{options.Action}'. Valid actions are: update, check, generate, download, status.")
_ => throw new ArgumentException($"Invalid action '{options.Action}'. Valid actions are: createmigrateproject, update, check, generate, download, status.")
};

context.Response.Results = ResponseResult.Create(
Expand Down Expand Up @@ -287,6 +297,37 @@ private static string HandleStatusAction(
return service.GetParameterStatus(context);
}

private static async Task<string> HandleCreateMigrateProjectActionAsync(
CommandContext context,
RequestOptions options,
CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(options.Location))
{
throw new ArgumentException("Location is required for creating an Azure Migrate project. Specify the Azure region (e.g., 'eastus', 'westus2').");
}

var deploymentHelper = context.GetService<AzureMigrateProjectHelper>();

var result = await deploymentHelper.CreateAzureMigrateProjectAsync(
options.MigrateProjectName!,
options.ResourceGroup!,
options.Location,
options.Subscription!,
tenant: null,
retryPolicy: null,
cancellationToken);

if (!result.HasData)
{
return $"Failed to create Azure Migrate project '{options.MigrateProjectName}'. The operation completed but no data was returned.";
}

return $"Azure Migrate project '{result.Name}' created successfully in resource group '{options.ResourceGroup}' at location '{result.Location}'.\n" +
$"Resource ID: {result.Id}\n" +
"You can now use the 'check', 'update', 'generate', and 'download' actions to generate a platform landing zone.";
}

/// <summary>
/// Result for the platform landing zone generate command.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Core;
using Azure.Mcp.Core.Options;
using Azure.Mcp.Core.Services.Azure;
using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Core.Services.Azure.Tenant;
using Azure.Mcp.Tools.AzureMigrate.Models;
using Azure.ResourceManager;

namespace Azure.Mcp.Tools.AzureMigrate.Helpers;

/// <summary>
/// Helper for creating Azure Migrate projects.
/// </summary>
public sealed class AzureMigrateProjectHelper(
ISubscriptionService subscriptionService,
ITenantService tenantService)
: BaseAzureResourceService(subscriptionService, tenantService)
{
private readonly ISubscriptionService _subscriptionService = subscriptionService;

private const string MigrateProjectResourceType = "Microsoft.Migrate/MigrateProjects";
private const string MigrateProjectApiVersion = "2020-06-01-preview";

/// <summary>
/// Creates an Azure Migrate project in the specified resource group.
/// </summary>
public async Task<MigrateProjectResult> CreateAzureMigrateProjectAsync(
string projectName,
string resourceGroup,
string location,
string subscription,
string? tenant = null,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
ValidateRequiredParameters(
(nameof(projectName), projectName),
(nameof(resourceGroup), resourceGroup),
(nameof(location), location),
(nameof(subscription), subscription));

try
{
ArmClient armClient = await CreateArmClientWithApiVersionAsync(
MigrateProjectResourceType,
MigrateProjectApiVersion,
null,
retryPolicy);

var subscriptionResource = await _subscriptionService.GetSubscription(subscription, cancellationToken: cancellationToken);
ResourceIdentifier projectId = new ResourceIdentifier(
$"/subscriptions/{subscriptionResource.Data.SubscriptionId}/resourceGroups/{resourceGroup}/providers/{MigrateProjectResourceType}/{projectName}");

var createContent = new MigrateProjectCreateContent
{
Location = location,
Properties = new MigrateProjectProperties()
};

var result = await CreateOrUpdateGenericResourceAsync(
armClient,
projectId,
location,
createContent,
AzureMigrateSerializerContext.Default.MigrateProjectCreateContent);

if (!result.HasData)
{
return new MigrateProjectResult(
HasData: false,
Id: null,
Name: null,
Type: null,
Location: null,
Properties: null);
}

return new MigrateProjectResult(
HasData: true,
Id: result.Data.Id.ToString(),
Name: result.Data.Name,
Type: result.Data.ResourceType.ToString(),
Location: result.Data.Location,
Properties: null);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Error creating Azure Migrate project '{projectName}': {ex.Message}", ex);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Serialization;
using Azure.Mcp.Tools.AzureMigrate.Models;

namespace Azure.Mcp.Tools.AzureMigrate.Helpers;

[JsonSerializable(typeof(MigrateProjectCreateContent))]
[JsonSerializable(typeof(MigrateProjectProperties))]
[JsonSerializable(typeof(MigrateProjectResult))]
[JsonSerializable(typeof(Dictionary<string, object>))]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
internal partial class AzureMigrateSerializerContext : JsonSerializerContext;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Serialization;

namespace Azure.Mcp.Tools.AzureMigrate.Models;

/// <summary>
/// Content for creating or updating an Azure Migrate project.
/// </summary>
public sealed class MigrateProjectCreateContent
{
/// <summary>
/// Gets or sets the Azure location for the migrate project.
/// </summary>
[JsonPropertyName("location")]
public string? Location { get; set; }

/// <summary>
/// Gets or sets the properties of the migrate project.
/// </summary>
[JsonPropertyName("properties")]
public MigrateProjectProperties? Properties { get; set; }
}

/// <summary>
/// Properties for an Azure Migrate project.
/// </summary>
public sealed class MigrateProjectProperties
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Azure.Mcp.Tools.AzureMigrate.Models;

/// <summary>
/// Result of a Migrate Project operation.
/// </summary>
public sealed record MigrateProjectResult(
bool HasData,
string? Id,
string? Name,
string? Type,
string? Location,
IDictionary<string, object>? Properties);
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ public static class PlatformLandingZoneOptionDefinitions
/// </summary>
public const string MigrateProjectResourceIdName = "migrate-project-resource-id";

/// <summary>
/// The location option name.
/// </summary>
public const string LocationName = "location";

/// <summary>
/// The scenario key for platform landing zone modification.
/// </summary>
Expand Down Expand Up @@ -279,4 +284,15 @@ public static class PlatformLandingZoneOptionDefinitions
Description = "The full resource ID of the Azure Migrate project for Platform Landing Zone (alternative to subscription/resourceGroup/migrateProjectName).",
Required = false
};

/// <summary>
/// The Azure region location for resource creation.
/// </summary>
public static readonly Option<string> Location = new(
$"--{LocationName}"
)
{
Description = "The Azure region location for creating new resources (e.g., 'eastus', 'westus2'). Required for 'createmigrateproject' action.",
Required = false
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,10 @@ public class RequestOptions : BaseAzureMigrateOptions
/// </summary>
[JsonPropertyName("migrateProjectResourceId")]
public string? MigrateProjectResourceId { get; set; }

/// <summary>
/// Gets or sets the Azure region location for resource creation.
/// </summary>
[JsonPropertyName("location")]
public string? Location { get; set; }
}
Loading