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 publishing to the dotnetbuilds storage account alongside dotnetcli #7877

Merged
merged 6 commits into from
Oct 4, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 18 additions & 0 deletions .vault-config/dotnetbuildskeys.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
storageLocation:
type: azure-key-vault
parameters:
subscription: 11c6037b-227b-4d63-bee1-18c7b68c3a40
name: dotnetbuildskeys

secrets:
dotnetbuilds-access-key:
type: azure-storage-key
parameters:
subscription: 11c6037b-227b-4d63-bee1-18c7b68c3a40
account: dotnetbuilds

dotnetbuilds-connection-string:
type: azure-storage-connection-string
parameters:
storageKeySecret: dotnetbuilds-access-key
account: dotnetbuilds
49 changes: 49 additions & 0 deletions .vault-config/dotnetbuildstokens.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
storageLocation:
type: azure-key-vault
parameters:
subscription: 11c6037b-227b-4d63-bee1-18c7b68c3a40
name: dotnetbuildstokens

references:
dotnetbuildskeys:
type: azure-key-vault
parameters:
subscription: 11c6037b-227b-4d63-bee1-18c7b68c3a40
name: dotnetbuildskeys

secrets:
dotnetbuilds-internal-container-uri:
type: azure-storage-container-sas-uri
parameters:
connectionString:
name: dotnetbuilds-connection-string
location: dotnetbuildskeys
permissions: rlwc
container: internal

dotnetbuilds-internal-container-checksum-uri:
type: azure-storage-container-sas-uri
parameters:
connectionString:
name: dotnetbuilds-connection-string
location: dotnetbuildskeys
permissions: rlwc
container: internal-checksums

dotnetbuilds-public-container-uri:
type: azure-storage-container-sas-uri
parameters:
connectionString:
name: dotnetbuilds-connection-string
location: dotnetbuildskeys
permissions: rlwc
container: public

dotnetbuilds-public-container-checksum-uri:
type: azure-storage-container-sas-uri
parameters:
connectionString:
name: dotnetbuilds-connection-string
location: dotnetbuildskeys
permissions: rlwc
container: public-checksums
5 changes: 5 additions & 0 deletions eng/publishing/v3/publish-assets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
timeoutInMinutes: 120
variables:
- group: DotNet-Symbol-Server-Pats
- group: DotNetBuilds storage account tokens
- name: BARBuildId
value: $[ dependencies.setupMaestroVars.outputs['setReleaseVars.BARBuildId'] ]
- name: IsStableBuild
Expand Down Expand Up @@ -98,6 +99,10 @@ jobs:
/p:UseStreamingPublishing='true'
/p:StreamingPublishingMaxClients=16
/p:NonStreamingPublishingMaxClients=12
/p:DotNetBuildsPublicUri='$(dotnetbuilds-public-container-uri)'
/p:DotNetBuildsPublicChecksumsUri='$(dotnetbuilds-public-container-checksum-uri)'
/p:DotNetBuildsInternalUri='$(dotnetbuilds-internal-container-uri)'
/p:DotNetBuildsInternalChecksumsUri='$(dotnetbuilds-internal-container-checksum-uri)'
- template: /eng/common/templates/steps/publish-logs.yml
parameters:
StageLabel: '${{ parameters.stageName }}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@
AzureProject="$(AzureProject)"
UseStreamingPublishing="$(UseStreamingPublishing)"
StreamingPublishingMaxClients="$(StreamingPublishingMaxClients)"
NonStreamingPublishingMaxClients="$(NonStreamingPublishingMaxClients)"/>
NonStreamingPublishingMaxClients="$(NonStreamingPublishingMaxClients)"
michellemcdaniel marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

When the feed overrides are present, we should write a test that ensures that an asset is only pushed to a target endpoint once. In the new configuration here, there are multiple target feed configs for an installer blob, for instance. That might cause a bad interaction when the target is overridden to be a specific feed.

DotNetBuildsPublicUri="$(DotNetBuildsPublicUri)"
DotNetBuildsPublicChecksumsUri="$(DotNetBuildsPublicChecksumsUri)"
DotNetBuildsInternalUri="$(DotNetBuildsInternalUri)"
DotNetBuildsInternalChecksumsUri="$(DotNetBuildsInternalChecksumsUri)"
/>
</Target>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public async Task FeedConfigParserTests2Async()
await task.ParseTargetFeedConfigAsync();
task.Log.HasLoggedErrors.Should().BeTrue();
buildEngine.BuildErrorEvents.Should().Contain(e =>
e.Message.Equals("Invalid feed config type 'MyUnknownFeedType'. Possible values are: AzDoNugetFeed, AzureStorageFeed"));
e.Message.Equals("Invalid feed config type 'MyUnknownFeedType'. Possible values are: AzDoNugetFeed, AzureStorageFeed, AzureStorageContainer"));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public void PublishToBothSymbolServerTest()
}

[Fact]
public void TemporarySymbolDirectoryDoesNotExists()
public async Task TemporarySymbolDirectoryDoesNotExists()
{
var buildEngine = new MockBuildEngine();
var task = new PublishArtifactsInManifestV3()
Expand All @@ -91,7 +91,7 @@ public void TemporarySymbolDirectoryDoesNotExists()
};
var path = TestInputs.GetFullPath("Symbol");
var buildAsset = new Dictionary<string, HashSet<Asset>>();
var publish = task.HandleSymbolPublishingAsync(path, MsdlToken, SymWebToken, "", false, buildAsset, null, path);
await task.HandleSymbolPublishingAsync(path, MsdlToken, SymWebToken, "", false, buildAsset, null, path);
Copy link
Member

Choose a reason for hiding this comment

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

That's... troubling. :-)

Assert.True(task.Log.HasLoggedErrors);
}

Expand All @@ -113,7 +113,6 @@ public void TemporarySymbolsDirectoryTest()
[Fact]
public void PublishSymbolApiIsCalledTest()
{
var publishTask = new PublishArtifactsInManifestV3();
var path = TestInputs.GetFullPath("Symbols");
string[] fileEntries = Directory.GetFiles(path);
var feedConfigsForSymbols = new HashSet<TargetFeedConfig>();
Expand All @@ -128,8 +127,6 @@ public void PublishSymbolApiIsCalledTest()
@internal: false,
allowOverwrite: true,
SymbolTargetType.SymWeb));
Dictionary<string, string> test =
publishTask.GetTargetSymbolServers(feedConfigsForSymbols, MsdlToken, SymWebToken);
Assert.True(PublishSymbolsHelper.PublishAsync(null,
path,
SymWebToken,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ public void StableFeeds(bool publishInstallersAndChecksums, bool isInternalBuild
AzureDevOpsFeedsKey,
buildEngine,
symbolTargetType,
"public",
"publicchecksums",
"internal",
"internalchecksums",
StablePackageFeed,
StableSymbolsFeed,
filesToExclude: FilesToExclude
Expand Down Expand Up @@ -275,6 +279,10 @@ public void NonStableAndInternal(bool publishInstallersAndChecksums)
AzureDevOpsFeedsKey,
buildEngine: buildEngine,
symbolTargetType,
"public",
"publicchecksums",
"internal",
"internalchecksums",
filesToExclude: FilesToExclude
);

Expand Down Expand Up @@ -385,6 +393,10 @@ public void NonStableAndPublic(bool publishInstallersAndChecksums)
AzureDevOpsFeedsKey,
buildEngine: buildEngine,
symbolTargetType,
"public",
"publicchecksums",
"internal",
"internalchecksums",
filesToExclude: FilesToExclude
);

Expand Down
34 changes: 34 additions & 0 deletions src/Microsoft.DotNet.Build.Tasks.Feed/src/AssetPublisher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Threading;
using Microsoft.Build.Utilities;
using Microsoft.DotNet.Maestro.Client.Models;
using Task = System.Threading.Tasks.Task;

namespace Microsoft.DotNet.Build.Tasks.Feed
{
public abstract class AssetPublisher : IDisposable
{
public TaskLoggingHelper Log { get; }
public abstract AddAssetLocationToAssetAssetLocationType LocationType { get; }

protected AssetPublisher(TaskLoggingHelper log)
{
Log = log;
Copy link
Member

Choose a reason for hiding this comment

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

Why not just let the base classes handle this if they want? Rather than forcing everyone to call base(log)?

This abstract class isn't really adding any functionality that an interface with the single "PublishAssetAsync" doesn't provide.

Copy link
Member Author

Choose a reason for hiding this comment

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

All of the implementations needed it, and calling base(log) isn't really difficult. An interface would play nicer with DI if we do it in the future, so I will change it.

}

public abstract Task PublishAssetAsync(string file, string blobPath, PushOptions options, SemaphoreSlim clientThrottle = null);
ChadNedzlek marked this conversation as resolved.
Show resolved Hide resolved

protected virtual void Dispose(bool disposing)
{
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
35 changes: 35 additions & 0 deletions src/Microsoft.DotNet.Build.Tasks.Feed/src/AssetPublisherFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.Build.Utilities;
using Microsoft.DotNet.Build.Tasks.Feed.Model;

namespace Microsoft.DotNet.Build.Tasks.Feed
{
public class AssetPublisherFactory
{
private readonly TaskLoggingHelper _log;

public AssetPublisherFactory(TaskLoggingHelper log)
{
_log = log;
}

public virtual AssetPublisher CreateAssetPublisher(TargetFeedConfig feedConfig, PublishArtifactsInManifestBase task)
ChadNedzlek marked this conversation as resolved.
Show resolved Hide resolved
{
switch (feedConfig.Type)
{
case FeedType.AzDoNugetFeed:
return new AzureDevOpsNugetFeedAssetPublisher(_log, feedConfig.TargetURL, feedConfig.Token, task);
case FeedType.AzureStorageFeed:
Copy link
Member

Choose a reason for hiding this comment

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

I believe this can be removed (maybe just stick an NYI in the case). We should not be publishing using Sleet any longer.

Copy link
Member Author

Choose a reason for hiding this comment

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

FeedType.AzureStorageFeed == publishing to dotnetcli, I don't think we can remove that. Unless the code does very different things than from what I interpreted.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm..I thought that was "AzureStorageContainer".

Copy link
Member Author

Choose a reason for hiding this comment

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

AzureStorageContainer is the new one I made, that uses a SAS token to publish, AzureStorageFeed is the old thing that looks and smells like sleet, but just strips the index.json off the end and publishes blobs like normal. It uses the account key.

Copy link
Member

Choose a reason for hiding this comment

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

Okay. Can you do a quick refactor on the AzureStorageFeed enum name to be more indicative of its purpose?

var action = new BlobFeedAction(feedConfig.TargetURL, feedConfig.Token, _log);
return new AzureStorageFeedAssetPublisher(action.AccountName, action.AccountKey, action.ContainerName, _log);
case FeedType.AzureStorageContainer:
return new AzureStorageContainerAssetPublisher(new Uri(feedConfig.TargetURL), _log);
default:
throw new NotImplementedException();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.Build.Utilities;
using Microsoft.DotNet.Build.Tasks.Feed.Model;
using Microsoft.DotNet.Maestro.Client.Models;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using Task = System.Threading.Tasks.Task;

namespace Microsoft.DotNet.Build.Tasks.Feed
{
public class AzureDevOpsNugetFeedAssetPublisher : AssetPublisher
{
private readonly string _targetUrl;
private readonly string _accessToken;
private readonly PublishArtifactsInManifestBase _task;
private readonly string _feedAccount;
private readonly string _feedVisibility;
private readonly string _feedName;
private readonly HttpClient _httpClient;

public AzureDevOpsNugetFeedAssetPublisher(TaskLoggingHelper log, string targetUrl, string accessToken, PublishArtifactsInManifestBase task) : base(log)
{
_targetUrl = targetUrl;
_accessToken = accessToken;
_task = task;

var parsedUri = Regex.Match(_targetUrl, PublishingConstants.AzDoNuGetFeedPattern);
if (!parsedUri.Success)
{
Log.LogError(
$"Azure DevOps NuGetFeed was not in the expected format '{PublishingConstants.AzDoNuGetFeedPattern}'");
return;
Copy link
Member

Choose a reason for hiding this comment

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

You can... return from a constructor? Is that... wise? This feels like it should throw, since nothing later has anything like "did the complete normally" which is a weird thing to have to ask.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea, you can return. This is this way because it was that way where I moved it from, but it should 100% be a throw, I will change it.

}
_feedAccount = parsedUri.Groups["account"].Value;
_feedVisibility = parsedUri.Groups["visibility"].Value;
_feedName = parsedUri.Groups["feed"].Value;

_httpClient = new HttpClient(new HttpClientHandler {CheckCertificateRevocationList = true})
{
Timeout = TimeSpan.FromSeconds(300),
Copy link
Member

Choose a reason for hiding this comment

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

Can you hoist this constant out (and other similar constants). We've had to update these a few times in the past and its never easy to actually find which ones to change.

Copy link
Member Author

Choose a reason for hiding this comment

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

will do.

DefaultRequestHeaders =
{
Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes($":{_accessToken}")))
},
};
}

protected override void Dispose(bool disposing)
{
_httpClient?.Dispose();
Copy link
Member

Choose a reason for hiding this comment

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

This needs to only happen in "disposing==true". If it's fale, the _httpClient might already be GC'd. (antoher reason maybe having the base class isn't a good idea.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

}

public override AddAssetLocationToAssetAssetLocationType LocationType => AddAssetLocationToAssetAssetLocationType.NugetFeed;

public override async Task PublishAssetAsync(string file, string blobPath, PushOptions options, SemaphoreSlim clientThrottle = null)
{
if (!file.EndsWith(GeneralUtils.PackageSuffix, StringComparison.OrdinalIgnoreCase))
{
Log.LogWarning(
$"AzDO feed publishing not available for blobs. Blob '{file}' was not published.");
return;
}

using var packageReader = new PackageArchiveReader(file);
Copy link
Member

Choose a reason for hiding this comment

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

You should wrap this {} and scope appropriately. I seem to remember changing this specifically a while back for either performance or correctness reasons. IIRC, PackageArchiveReader took an exclusive lock on the file, and depending on disposal time (since in this configuration the scope goes to the end of the method), we got occasional lock issues.

Anyways, scoping down to the minimal required scope is also good practice.

Copy link
Member Author

Choose a reason for hiding this comment

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

done.

PackageIdentity packageIdentity = packageReader.GetIdentity();
string id = packageIdentity.Id;
string version = packageIdentity.Version.ToString();

try
{
var config = new TargetFeedConfig(default, _targetUrl, default, default);
await _task.PushNugetPackageAsync(config, _httpClient, file, id, version, _feedAccount, _feedVisibility, _feedName);
}
catch (Exception e)
{
Log.LogErrorFromException(e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading;
using Azure.Storage.Blobs;
using Microsoft.Build.Utilities;
using Microsoft.DotNet.Maestro.Client.Models;
using Task = System.Threading.Tasks.Task;

namespace Microsoft.DotNet.Build.Tasks.Feed
{
public abstract class AzureStorageAssetPublisher : AssetPublisher
{
protected AzureStorageAssetPublisher(TaskLoggingHelper log)
: base(log)
{
}

public override AddAssetLocationToAssetAssetLocationType LocationType => AddAssetLocationToAssetAssetLocationType.Container;

public abstract BlobClient CreateBlobClient(string blobPath);

public override async Task PublishAssetAsync(string file, string blobPath, PushOptions options, SemaphoreSlim clientThrottle = null)
{
using (await SemaphoreLock.LockAsync(clientThrottle))
{
blobPath = blobPath.Replace("\\", "/");
var blobClient = CreateBlobClient(blobPath);
if (!options.AllowOverwrite && await blobClient.ExistsAsync())
{
if (options.PassIfExistingItemIdentical)
Copy link
Member

Choose a reason for hiding this comment

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

I think this option can be just removed at this point.

Copy link
Member Author

Choose a reason for hiding this comment

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

Its set to true in all cases I can see, so I don't see how we can remove it.

Copy link
Member

Choose a reason for hiding this comment

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

I mean remove the conditional altogether. I don't see us ever passing false for this.

{
if (!await blobClient.IsFileIdenticalToBlobAsync(file))
{
Log.LogError($"Asset '{file}' already exists with different contents at '{blobPath}'");
}

return;
}

Log.LogError($"Asset '{file}' already exists at '{blobPath}'");
return;
}

Log.LogMessage($"Uploading '{file}' to '{blobPath}'");
await blobClient.UploadAsync(file);
}
}
}
}
Loading