Skip to content

Commit b26757e

Browse files
authored
Merge pull request #10330 from NuGet/dev
[ReleasePrep][2025.01.28] RI of dev into main
2 parents b55d335 + 8144598 commit b26757e

28 files changed

Lines changed: 384 additions & 141 deletions

File tree

Directory.Packages.props

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
8181
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
8282
<PackageVersion Include="Microsoft.Identity.Client" Version="4.65.0" />
83-
<PackageVersion Include="Microsoft.Identity.Web" Version="3.2.2" />
83+
<PackageVersion Include="Microsoft.Identity.Web" Version="3.6.2" />
8484
<PackageVersion Include="Microsoft.Net.Http" Version="2.2.29" />
8585
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
8686
<PackageVersion Include="Microsoft.Owin.Host.SystemWeb" Version="4.2.2" />
@@ -153,4 +153,7 @@
153153
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
154154
<PackageVersion Include="xunit" Version="2.9.0" />
155155
</ItemGroup>
156-
</Project>
156+
<ItemGroup>
157+
<GlobalPackageReference Include="PolySharp" Version="1.15.0" />
158+
</ItemGroup>
159+
</Project>

NuGet.config

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<clear />
1313
<packageSource key="NuGet.org">
1414
<package pattern="Antlr" />
15-
<package pattern="AngleSharp.*" />
15+
<package pattern="AngleSharp.*" />
1616
<package pattern="AngleSharp" />
1717
<package pattern="Autofac.*" />
1818
<package pattern="Autofac" />
@@ -52,6 +52,7 @@
5252
<package pattern="Owin" />
5353
<package pattern="Polly.Extensions.Http" />
5454
<package pattern="Polly" />
55+
<package pattern="PolySharp" />
5556
<package pattern="Portable.BouncyCastle" />
5657
<package pattern="RazorEngine" />
5758
<package pattern="RouteMagic" />

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
"rollForward": "latestFeature",
55
"allowPrerelease": false
66
}
7-
}
7+
}

src/CopyAzureContainer/AzureContainerInfo.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
namespace CopyAzureContainer
55
{
66
public class AzureContainerInfo
77
{
88
public string StorageAccountName { get; set; }
9-
public string StorageAccountKey { get; set; }
109
public string StorageSasToken { get; set; }
1110
public string ContainerName { get; set; }
1211
}

src/CopyAzureContainer/Configuration/CopyAzureContainerConfiguration.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System.Collections.Generic;
@@ -9,7 +9,6 @@ public class CopyAzureContainerConfiguration
99
{
1010
public int? BackupDays { get; set; }
1111
public string DestStorageAccountName { get; set; }
12-
public string DestStorageKeyValue { get; set; }
1312
public string DestStorageSasValue { get; set; }
1413
public List<AzureContainerInfo> SourceContainers { get; set; }
1514
}

src/CopyAzureContainer/CopyAzureContainerJob.cs

Lines changed: 127 additions & 92 deletions
Large diffs are not rendered by default.

src/CopyAzureContainer/README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Overview
2+
3+
TLDR: It copies containers from a source storage to a destination storage to create backup files for later restore if needed. Also, it uses both Azure Storage Blob SDK and AzCopy tool.
4+
5+
This job is a tool that copies all the content from multiple source containers from a source storage to a destination storage that creates a backup container for each of the source containers. Each backup container on the destination storage is deleted if they have been created before an specified **BackupDays**. These backup containers can be used to restore all files on their source.
6+
7+
This job uses Azure Blob Storage SDK to delete and create containers on the destination storage and the [AzCopy v10](https://learn.microsoft.com/azure/storage/common/storage-use-azcopy-v10?tabs=dnf) tool to perform the copy operation between storages.
8+
9+
When the job runs, it creates a new folder locally where AzCopy files like logs will be stored, these files will also be uploaded to the destination storage in the logs container for investigation in case something wrong happened in the AzCopy operation. This folder is cleaned on every new run.
10+
11+
This job uses 3 types of login defined on the appsettings file:
12+
* If Sas tokens are provided:
13+
* For Storage SDK: It uses the `AzureSasCredential`.
14+
* For AzCopy: The job adds the sas tokens for the storages on the copy commands.
15+
* If Managed Identity is provided:
16+
* For Storage SDK: It creates clients with `DefaultAzureCredential` or `ManagedIdentityCredential` if a managed identity client id is provided.
17+
* For AzCopy:
18+
* NOTE: Only used for system or user assigned MSI on VMs not for testing locally.
19+
* It uses AzCopy `AZCOPY_AUTO_LOGIN_TYPE=MSI` environment variable.
20+
* If a managed identity client id is provided it stores it in `AZCOPY_MSI_CLIENT_ID=<msi-client-id>` environment variable.
21+
* When Debugging locally for AzCopy if no SAS tokens are provided:
22+
* The CopyAzureContainer job will run the `azcopy login` command that will generate a code on the terminal, you should just follow the instructions to login.
23+
24+
# Algorithm
25+
26+
1. It creates a logs container on the destination storage.
27+
1. For each specified source container does the following:
28+
1. Deletes all destination containers that have been created before the specified **BackupDays** that has the source container prefix.
29+
1. e.g. If a source container is `catalog` and the destination backup container is `catalog-<date>`. it will delete all of the containers that starts with `catalog` if they are older than the BackupDays.
30+
1. It performs the copy operation for that source container, the steps are the following:
31+
1. It creates a local folder to store AzCopy files.
32+
1. `AZCOPY_JOB_PLAN_LOCATION` environment variable tells AzCopy where to store job plan files.
33+
1. `AZCOPY_LOG_LOCATION` environment variable tells AzCopy where to store log files.
34+
1. It creates a new container on the destination storage to contain all the files from the source container.
35+
1. This new container name is created with the format `<source container name>-<date>`. e.g. `catalog` container on source storage is `catalog-2025010800` on the destination storage. This to keep multiple backups of the same source container.
36+
1. It generates the commands that AzCopy will use to copy the container contents, and the logs.
37+
1. If you are on Debug mode and didn't provide SAS tokens for storage access it will run the `azcopy login` command, this will provide instructions on the terminal on how to authenticate.
38+
1. Then it will run the AzCopy tool to run the copy command to copy the source container content to the destination container previously created.
39+
1. After it copied all the content, it will upload the logs on the destination storage logs container.
40+
41+
42+
# Running the job
43+
44+
## Prerequisites
45+
46+
1. Azure Storage account.
47+
* You can reuse an storage account or create a new one.
48+
* Make sure to have at least a container with files on your source storage to test the behavior.
49+
* If you are using managed identities for Storage SDK usage make sure to have `Storage Blob Data Contributor` access on your identity.
50+
1. Create the appsettings.json file.
51+
* This will contained the information needed for the job to run.
52+
* Example of a configuration file using SAS tokens.
53+
```
54+
{
55+
"CopyAzureContainer": {
56+
"BackupDays": 14, // Amount of days the destination storage should keep backup containers before deletion.
57+
"DestStorageAccountName": "destinationstorage", // Destination storage account name
58+
"DestStorageSasValue": "<destination storage sas token>", // Destination storage sas token
59+
"SourceContainers": [
60+
{
61+
"StorageAccountName": "sourcestorage1", // Source storage account name
62+
"StorageSasToken": "<source storage sas token>", // Source storage sas token
63+
"ContainerName": "sourcecontainer" // Source container that will be copied to the destination
64+
}
65+
]
66+
},
67+
"KeyVault_VaultName": "PLACEHOLDER",
68+
"KeyVault_UseManagedIdentity": true
69+
}
70+
```
71+
72+
* Example of a configuration file using MSI. (just remove the properties for sas values and add the `Storage_UseManagedIdentity` property)
73+
```
74+
{
75+
"CopyAzureContainer": {
76+
"BackupDays": 14,
77+
"DestStorageAccountName": "destinationstorage",
78+
"SourceContainers": [
79+
{
80+
"StorageAccountName": "sourcestorage1",
81+
"ContainerName": "sourcecontainer"
82+
}
83+
]
84+
},
85+
"KeyVault_VaultName": "PLACEHOLDER",
86+
"KeyVault_UseManagedIdentity": true,
87+
"Storage_UseManagedIdentity": true
88+
}
89+
```
90+
1. Download AzCopy tool
91+
1. Execute the `Scripts/InstallAzCopy.ps1` script to download AzCopy tool.
92+
1. Make sure the `azcopy.exe` file was added on your `bin\Debug\tools\azcopy` or directory where your binaries are like `CopyAzureContainer\bin\Debug\net472\tools\azcopy`.
93+
94+
## Execute the job
95+
96+
1. Build the project and go to where the binaries are.
97+
2. Place the appsettings.json file in the same folder as the binaries.
98+
3. Make sure you have the AzCopy tool downloaded on `<path to your binaries>/tool/azcopy/azcopy.exe`
99+
4. Wait until the job is completed.
100+
101+
102+
# Resources
103+
104+
* [Get started with AzCopy](https://learn.microsoft.com/azure/storage/common/storage-use-azcopy-v10?tabs=dnf)
105+
* [Authorize access to blobs and files with AzCopy and Microsoft Entra ID](https://learn.microsoft.com/azure/storage/common/storage-use-azcopy-authorize-azure-active-directory)
106+
* [AzCopy v10 configuration settings (Azure Storage)](https://learn.microsoft.com/azure/storage/common/storage-ref-azcopy-configuration-settings)
107+
* [Troubleshoot issues in AzCopy v10](https://learn.microsoft.com/troubleshoot/azure/azure-storage/blobs/connectivity/storage-use-azcopy-troubleshoot)

src/Gallery.CredentialExpiration/Configuration/InitializationConfiguration.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using NuGet.Jobs.Configuration;
@@ -11,6 +11,8 @@ public class InitializationConfiguration : MessageServiceConfiguration
1111

1212
public string DataStorageAccount { get; set; }
1313

14+
public string DataStorageAccountUrl { get; set; }
15+
1416
public string EmailPublisherConnectionString { get; set; }
1517

1618
public string EmailPublisherTopicName { get; set; }

src/Gallery.CredentialExpiration/Job.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
using System.Threading;
1010
using System.Threading.Tasks;
1111
using Autofac;
12+
using Azure.Core;
13+
using Azure.Identity;
1214
using Azure.Storage.Blobs;
1315
using Gallery.CredentialExpiration.Models;
1416
using Microsoft.Extensions.Configuration;
@@ -51,8 +53,10 @@ public override void Init(IServiceContainer serviceContainer, IDictionary<string
5153
InitializationConfiguration);
5254

5355
FromAddress = new MailAddress(InitializationConfiguration.MailFrom);
54-
55-
var storageAccount = new BlobServiceClientFactory(AzureStorageFactory.PrepareConnectionString(InitializationConfiguration.DataStorageAccount));
56+
57+
var tokenCredential = _serviceProvider.GetRequiredService<TokenCredential>();
58+
var storageAccount = new BlobServiceClientFactory(new Uri(InitializationConfiguration.DataStorageAccountUrl), tokenCredential);
59+
5660
var storageFactory = new AzureStorageFactory(
5761
storageAccount,
5862
InitializationConfiguration.ContainerName,

src/NuGet.Jobs.Common/StorageAccountExtensions.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using Autofac;
66
using Autofac.Builder;
7+
using Azure.Core;
78
using Azure.Data.Tables;
89
using Azure.Identity;
910
using Azure.Storage.Blobs;
@@ -22,7 +23,8 @@ public static IServiceCollection ConfigureStorageMsi(
2223
this IServiceCollection serviceCollection,
2324
IConfiguration configuration,
2425
string storageUseManagedIdentityPropertyName = null,
25-
string storageManagedIdentityClientIdPropertyName = null)
26+
string storageManagedIdentityClientIdPropertyName = null,
27+
string localDevelopmentPropertyName = null)
2628
{
2729
if (serviceCollection == null)
2830
{
@@ -35,9 +37,12 @@ public static IServiceCollection ConfigureStorageMsi(
3537

3638
storageUseManagedIdentityPropertyName ??= Constants.StorageUseManagedIdentityPropertyName;
3739
storageManagedIdentityClientIdPropertyName ??= Constants.StorageManagedIdentityClientIdPropertyName;
40+
localDevelopmentPropertyName ??= Constants.ConfigureForLocalDevelopment;
3841

3942
string useManagedIdentityStr = configuration[storageUseManagedIdentityPropertyName];
43+
string localDevelopmentStr = configuration[localDevelopmentPropertyName];
4044
bool useManagedIdentity = false;
45+
bool setupLocalDevelopment = false;
4146

4247
string managedIdentityClientId = string.IsNullOrWhiteSpace(configuration[storageManagedIdentityClientIdPropertyName])
4348
? configuration[Constants.ManagedIdentityClientIdKey]
@@ -47,6 +52,21 @@ public static IServiceCollection ConfigureStorageMsi(
4752
{
4853
useManagedIdentity = bool.Parse(useManagedIdentityStr);
4954
}
55+
56+
if (!string.IsNullOrWhiteSpace(localDevelopmentStr))
57+
{
58+
setupLocalDevelopment = bool.Parse(localDevelopmentStr);
59+
}
60+
61+
if (setupLocalDevelopment)
62+
{
63+
serviceCollection.AddSingleton<TokenCredential>(new DefaultAzureCredential());
64+
}
65+
else
66+
{
67+
serviceCollection.AddSingleton<TokenCredential>(new ManagedIdentityCredential(managedIdentityClientId));
68+
}
69+
5070
return serviceCollection.Configure<StorageMsiConfiguration>(storageConfiguration =>
5171
{
5272
storageConfiguration.UseManagedIdentity = useManagedIdentity;

0 commit comments

Comments
 (0)