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 Reset command #118

Merged
merged 6 commits into from
Aug 8, 2023
Merged
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
28 changes: 26 additions & 2 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AMSMigrate.Ams;
using AMSMigrate.ams;
using AMSMigrate.Ams;
using AMSMigrate.Azure;
using AMSMigrate.Contracts;
using AMSMigrate.Local;
Expand Down Expand Up @@ -75,6 +76,21 @@ This command forcefully removes the Azure Media Services (AMS) account.
await CleanupAsync(context, cleanupOptions, context.GetCancellationToken());
});

var resetOptionsBinder = new ResetOptionsBinder();
var resetCommand = resetOptionsBinder.GetCommand("reset", @"Reset assets back to their original NotMigrated state
Examples to reset assets in the AMS account:
reset -s <subscriptionid> -g <resourcegroup> -n <account> -c all
This command will forcibly revert all assets in source account to their initial NotMigrated state. By default, this parameter is set to ""all"".
reset -s <subscriptionid> -g <resourcegroup> -n <account> -c failed
This command will forcibly revert migrated assets that have failed back to their original NotMigrated state.");
rootCommand.Add(resetCommand);
resetCommand.SetHandler(
async context =>
{
var resetOptions =resetOptionsBinder.GetValue(context.BindingContext);
await ResetAsync(context, resetOptions, context.GetCancellationToken());
});

// disable storage migrate option until ready
/*
var storageOptionsBinder = new StorageOptionsBinder();
Expand Down Expand Up @@ -223,7 +239,15 @@ static async Task CleanupAsync(
await ActivatorUtilities.CreateInstance<CleanupCommand>(provider, cleanupOptions)
.MigrateAsync(cancellationToken);
}

static async Task ResetAsync(
InvocationContext context,
ResetOptions resetOptions,
CancellationToken cancellationToken)
{
var provider = context.BindingContext.GetRequiredService<IServiceProvider>();
await ActivatorUtilities.CreateInstance<ResetCommand>(provider, resetOptions)
.MigrateAsync(cancellationToken);
}

static async Task MigrateKeysAsync(
InvocationContext context,
Expand Down
48 changes: 48 additions & 0 deletions ResetOptionsBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using AMSMigrate.Contracts;
using FFMpegCore.Enums;
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Binding;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AMSMigrate
{
internal class ResetOptionsBinder : BinderBase<ResetOptions>
{
private readonly Option<string> _sourceAccount = new Option<string>(
aliases: new[] { "--source-account-name", "-n" },
description: "Azure Media Services Account.")
{
IsRequired = true,
Arity = ArgumentArity.ExactlyOne
};

private readonly Option<string> _category = new Option<string>(
aliases: new[] { "--category", "-c" },
()=>"all",
description: "Define two categories: \"all\" and \"failed\". The \"all\" category encompasses a complete reset of all assets within the account, regardless of their migration status. By default, this parameter is set to \"all\". The \"failed\" category exclusively pertains to resetting only those assets that have encountered migration failures, reverting them back to their non-migrated state.")
{
IsRequired = false
};
public ResetOptions GetValue(BindingContext context) => GetBoundValue(context);

public Command GetCommand(string name, string description)
{
var command = new Command(name, description);
command.AddOption(_sourceAccount);
command.AddOption(_category);
return command;
}

protected override ResetOptions GetBoundValue(BindingContext bindingContext)
{
return new ResetOptions(
bindingContext.ParseResult.GetValueForOption(_sourceAccount)!,
bindingContext.ParseResult.GetValueForOption(_category)!
);
}
}
}
3 changes: 2 additions & 1 deletion ams/AssetMigrationTracker.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AMSMigrate.Contracts;
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;

Expand Down Expand Up @@ -64,7 +65,7 @@ internal class AssetMigrationTracker : IMigrationTracker<BlobContainerClient, As
internal const string MigrateResultKey = "MigrateResult";
internal const string ManifestNameKey = "ManifestName";
internal const string OutputPathKey = "OutputPath";

public async Task<AssetMigrationResult> GetMigrationStatusAsync(BlobContainerClient container, CancellationToken cancellationToken)
{
BlobContainerProperties properties = await container.GetPropertiesAsync(cancellationToken: cancellationToken);
Expand Down
35 changes: 19 additions & 16 deletions ams/CleanupCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Azure;
using Azure.Core;
using Azure.ResourceManager.Media;
using Azure.ResourceManager.Media.Models;
using Azure.Storage.Blobs;
using Microsoft.Extensions.Logging;
using Spectre.Console;
Expand Down Expand Up @@ -46,16 +47,10 @@ public override async Task MigrateAsync(CancellationToken cancellationToken)
}

Dictionary<string, bool> stats = new Dictionary<string, bool>();
var totalAssets = await QueryMetricAsync(
account.Id.ToString(),
"AssetCount",
cancellationToken: cancellationToken);

_logger.LogInformation("The total asset count of the media account is {count}.", totalAssets);
AsyncPageable<MediaAssetResource> assets;

//clean up asset
var resourceFilter = _options.IsCleanUpAccount? null: GetAssetResourceFilter(_options.ResourceFilter, null, null);
var resourceFilter = _options.IsCleanUpAccount ? null : GetAssetResourceFilter(_options.ResourceFilter, null, null);

var orderBy = "properties/created";
assets = account.GetMediaAssets()
Expand All @@ -64,7 +59,7 @@ public override async Task MigrateAsync(CancellationToken cancellationToken)

foreach (var asset in assetList)
{
var result = await CleanUpAssetAsync(_options.IsCleanUpAccount||_options.IsForceCleanUpAsset,account, asset, cancellationToken);
var result = await CleanUpAssetAsync(_options.IsCleanUpAccount || _options.IsForceCleanUpAsset, account, asset, cancellationToken);
stats.Add(asset.Data.Name, result);
}
WriteSummary(stats, false);
Expand Down Expand Up @@ -111,6 +106,10 @@ private async Task<bool> CleanUpAccountAsync(MediaServicesAccountResource accoun
{
foreach (var streamingEndpoint in endpoints)
{
if (streamingEndpoint.Data.ResourceState == StreamingEndpointResourceState.Running)
{
await streamingEndpoint.StopAsync(WaitUntil.Completed);
}
await streamingEndpoint.DeleteAsync(WaitUntil.Completed);
}
}
Expand All @@ -125,10 +124,14 @@ private async Task<bool> CleanUpAccountAsync(MediaServicesAccountResource accoun
{
foreach (var liveEvent in liveevents)
{
if (liveEvent.Data.ResourceState == LiveEventResourceState.Running)
{
await liveEvent.StopAsync(WaitUntil.Completed, new LiveEventActionContent() { RemoveOutputsOnStop = true });
}
await liveEvent.DeleteAsync(WaitUntil.Completed);
}
}

var deleteOperation = await account.DeleteAsync(WaitUntil.Completed);

if (deleteOperation.HasCompleted && deleteOperation.GetRawResponse().Status == 200)
Expand All @@ -149,7 +152,7 @@ private async Task<bool> CleanUpAccountAsync(MediaServicesAccountResource accoun
return false;
}
}
private async Task<bool> CleanUpAssetAsync(bool isForcedelete,MediaServicesAccountResource account, MediaAssetResource asset, CancellationToken cancellationToken)
private async Task<bool> CleanUpAssetAsync(bool isForcedelete, MediaServicesAccountResource account, MediaAssetResource asset, CancellationToken cancellationToken)
{
try
{
Expand All @@ -162,11 +165,11 @@ private async Task<bool> CleanUpAssetAsync(bool isForcedelete,MediaServicesAccou

return false;
}
// The asset container exists, try to check the metadata list first.
if (isForcedelete||(_tracker.GetMigrationStatusAsync(container, cancellationToken).Result.Status == MigrationStatus.Completed))
{

// The asset container exists, try to check the metadata list first.

if (isForcedelete || (_tracker.GetMigrationStatusAsync(container, cancellationToken).Result.Status == MigrationStatus.Completed))
{
var locator = await account.GetStreamingLocatorAsync(asset, cancellationToken);
if (locator != null)
{
Expand Down
95 changes: 95 additions & 0 deletions ams/ResetCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using AMSMigrate.Ams;
using AMSMigrate.Contracts;
using Azure;
using Azure.Core;
using Azure.ResourceManager.Media;
using Azure.ResourceManager.Media.Models;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Microsoft.Extensions.Logging;
using Spectre.Console;

namespace AMSMigrate.ams
{
internal class ResetCommand : BaseMigrator
{
private readonly ILogger _logger;
private readonly ResetOptions _options;
private readonly IMigrationTracker<BlobContainerClient, AssetMigrationResult> _tracker;
internal const string AssetTypeKey = "AssetType";
internal const string MigrateResultKey = "MigrateResult";
internal const string ManifestNameKey = "ManifestName";
internal const string OutputPathKey = "OutputPath";

public ResetCommand(GlobalOptions globalOptions,
ResetOptions resetOptions,
IAnsiConsole console,
TokenCredential credential,
IMigrationTracker<BlobContainerClient, AssetMigrationResult> tracker,
ILogger<ResetCommand> logger)
: base(globalOptions, console, credential)
{
_options = resetOptions;
_logger = logger;
_tracker = tracker;
}

public override async Task MigrateAsync(CancellationToken cancellationToken)
{
var account = await GetMediaAccountAsync(_options.AccountName, cancellationToken);
_logger.LogInformation("Begin reset assets on account: {name}", account.Data.Name);

AsyncPageable<MediaAssetResource> assets = account.GetMediaAssets()
.GetAllAsync(cancellationToken: cancellationToken);
List<MediaAssetResource>? assetList = await assets.ToListAsync(cancellationToken);
int resetedAssetCount = 0;
foreach (var asset in assetList)
{
var (storage, _) = await _resourceProvider.GetStorageAccount(asset.Data.StorageAccountName, cancellationToken);
var container = storage.GetContainer(asset);
if (!await container.ExistsAsync(cancellationToken))
{
_logger.LogWarning("Container {name} missing for asset {asset}", container.Name, asset.Data.Name);
return;
}

if (_options.category.Equals("all", StringComparison.OrdinalIgnoreCase) || (_tracker.GetMigrationStatusAsync(container, cancellationToken).Result.Status == MigrationStatus.Failed))
{
try
{
BlobContainerProperties properties = await container.GetPropertiesAsync(cancellationToken: cancellationToken);

if (properties?.Metadata != null && properties.Metadata.Count == 0)
{
_logger.LogInformation($"Container '{container.Name}' does not have metadata.");
}
else
{ // Clear container metadata
properties?.Metadata?.Remove(MigrateResultKey);
properties?.Metadata?.Remove(AssetTypeKey);
properties?.Metadata?.Remove(OutputPathKey);
properties?.Metadata?.Remove(ManifestNameKey);
var deleteOperation = await container.SetMetadataAsync(properties?.Metadata);
if (deleteOperation.GetRawResponse().Status == 200)
{
_logger.LogInformation($"Meta data in Container '{container.Name}' is deleted successfully.");
resetedAssetCount++;
}
else
{
_logger.LogInformation($"Meta data in Container '{container.Name}' does not exist or was not deleted.");
}
}

}
catch (Exception ex)
{
_logger.LogError($"An unexpected error occurred: {ex.Message}");
}

}
}
_logger.LogDebug($"{resetedAssetCount} out of {assetList.Count} assets has been reseted.");
}
}
}
12 changes: 12 additions & 0 deletions contracts/ResetOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

namespace AMSMigrate.Contracts
{
/// <summary>
/// It holds the options for cleanup commands.
/// </summary>
public record ResetOptions(
string AccountName,
string category
);

}