Skip to content

Commit

Permalink
Allow specifying custom properties for PublishPipelineArtifact task (#…
Browse files Browse the repository at this point in the history
…3615)

* Allow specifying custom properties for PublisPipelineArtifact task

* Ensure that custom pipeline properties are prefixed with user-

* Update PublishPipelineArtifactTask as well

* Pretty print strings.json file

Co-authored-by: fadnavistanmay <[email protected]>
Co-authored-by: Nikita Ezzhev <[email protected]>
  • Loading branch information
3 people authored Jan 24, 2022
1 parent 11a9d2d commit 8ea28eb
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/Agent.Plugins/Artifact/PipelineArtifactConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ public class PipelineArtifactConstants
public const string RootId = "RootId";
public const string SaveCache = "SaveCache";
public const string FileShareArtifact = "filepath";
public const string CustomPropertiesPrefix = "user-";
}
}
12 changes: 8 additions & 4 deletions src/Agent.Plugins/Artifact/PipelineArtifactServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ internal async Task UploadAsync(
int pipelineId,
string name,
string source,
IDictionary<string, string> properties,
CancellationToken cancellationToken)
{
VssConnection connection = context.VssConnection;
Expand Down Expand Up @@ -69,10 +70,13 @@ internal async Task UploadAsync(

// 2) associate the pipeline artifact with an build artifact
BuildServer buildServer = new BuildServer(connection);
Dictionary<string, string> propertiesDictionary = new Dictionary<string, string>();
propertiesDictionary.Add(PipelineArtifactConstants.RootId, result.RootId.ValueString);
propertiesDictionary.Add(PipelineArtifactConstants.ProofNodes, StringUtil.ConvertToJson(result.ProofNodes.ToArray()));
propertiesDictionary.Add(PipelineArtifactConstants.ArtifactSize, result.ContentSize.ToString());

var propertiesDictionary = new Dictionary<string, string>(properties ?? new Dictionary<string, string>())
{
{ PipelineArtifactConstants.RootId, result.RootId.ValueString },
{ PipelineArtifactConstants.ProofNodes, StringUtil.ConvertToJson(result.ProofNodes.ToArray()) },
{ PipelineArtifactConstants.ArtifactSize, result.ContentSize.ToString() }
};

BuildArtifact buildArtifact = await AsyncHttpRetryHelper.InvokeAsync(
async () =>
Expand Down
32 changes: 31 additions & 1 deletion src/Agent.Plugins/PipelineArtifact/PipelineArtifactPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
using Agent.Sdk;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.Services.Content.Common.Tracing;
using System.Linq;
using System.Text.Json;

namespace Agent.Plugins.PipelineArtifact
{
Expand Down Expand Up @@ -64,6 +66,8 @@ public class PublishPipelineArtifactTask : PipelineArtifactTaskPluginBase
// create a normalized identifier-compatible string (A-Z, a-z, 0-9, -, and .) and remove .default since it's redundant
public static readonly Regex jobIdentifierRgx = new Regex("[^a-zA-Z0-9 - .]", RegexOptions.Compiled | RegexOptions.CultureInvariant);

private const string customProperties = "properties";

protected override async Task ProcessCommandInternalAsync(
AgentTaskPluginExecutionContext context,
string targetPath,
Expand Down Expand Up @@ -105,12 +109,38 @@ protected override async Task ProcessCommandInternalAsync(
throw new FileNotFoundException(StringUtil.Loc("PathDoesNotExist", targetPath));
}

string propertiesStr = context.GetInput(customProperties, required: false);
IDictionary<string, string> properties = ParseCustomProperties(propertiesStr);

// Upload to VSTS BlobStore, and associate the artifact with the build.
context.Output(StringUtil.Loc("UploadingPipelineArtifact", fullPath, buildId));
PipelineArtifactServer server = new PipelineArtifactServer(tracer);
await server.UploadAsync(context, projectId, buildId, artifactName, fullPath, token);
await server.UploadAsync(context, projectId, buildId, artifactName, fullPath, properties, token);
context.Output(StringUtil.Loc("UploadArtifactFinished"));
}
private IDictionary<string, string> ParseCustomProperties(string properties)
{
if (string.IsNullOrWhiteSpace(properties))
{
return null;
}

try
{
var propertyBag = StringUtil.ConvertFromJson<IDictionary<string, string>>(properties);
var prefixMissing = propertyBag.Keys.FirstOrDefault(k => !k.StartsWith(PipelineArtifactConstants.CustomPropertiesPrefix));
if (!string.IsNullOrWhiteSpace(prefixMissing))
{
throw new InvalidOperationException(StringUtil.Loc("ArtifactCustomPropertyInvalid", prefixMissing));
}

return propertyBag;
}
catch (JsonException)
{
throw new ArgumentException(StringUtil.Loc("ArtifactCustomPropertiesNotJson", properties));
}
}

private string NormalizeJobIdentifier(string jobIdentifier)
{
Expand Down
31 changes: 30 additions & 1 deletion src/Agent.Plugins/PipelineArtifact/PipelineArtifactPluginV1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Services.Content.Common.Tracing;
using Newtonsoft.Json;

namespace Agent.Plugins.PipelineArtifact
{
Expand Down Expand Up @@ -63,6 +64,7 @@ public class PublishPipelineArtifactTaskV1 : PipelineArtifactTaskPluginBaseV1
private static readonly Regex jobIdentifierRgx = new Regex("[^a-zA-Z0-9 - .]", RegexOptions.Compiled | RegexOptions.CultureInvariant);
private const string pipelineType = "pipeline";
private const string fileShareType = "filepath";
private const string customProperties = "properties";

protected override async Task ProcessCommandInternalAsync(
AgentTaskPluginExecutionContext context,
Expand All @@ -83,6 +85,9 @@ protected override async Task ProcessCommandInternalAsync(

string defaultWorkingDirectory = context.Variables.GetValueOrDefault("system.defaultworkingdirectory").Value;

string propertiesStr = context.GetInput(customProperties, required: false);
IDictionary<string, string> properties = ParseCustomProperties(propertiesStr);

bool onPrem = !String.Equals(context.Variables.GetValueOrDefault(WellKnownDistributedTaskVariables.ServerType)?.Value, "Hosted", StringComparison.OrdinalIgnoreCase);
if (onPrem)
{
Expand Down Expand Up @@ -138,7 +143,7 @@ protected override async Task ProcessCommandInternalAsync(
// Upload to VSTS BlobStore, and associate the artifact with the build.
context.Output(StringUtil.Loc("UploadingPipelineArtifact", fullPath, buildId));
PipelineArtifactServer server = new PipelineArtifactServer(tracer);
await server.UploadAsync(context, projectId, buildId, artifactName, fullPath, token);
await server.UploadAsync(context, projectId, buildId, artifactName, fullPath, properties, token);
context.Output(StringUtil.Loc("UploadArtifactFinished"));

}
Expand All @@ -161,6 +166,30 @@ protected override async Task ProcessCommandInternalAsync(
}
}

private IDictionary<string, string> ParseCustomProperties(string properties)
{
if (string.IsNullOrWhiteSpace(properties))
{
return null;
}

try
{
var propertyBag = StringUtil.ConvertFromJson<IDictionary<string, string>>(properties);
var prefixMissing = propertyBag.Keys.FirstOrDefault(k => !k.StartsWith(PipelineArtifactConstants.CustomPropertiesPrefix));
if (!string.IsNullOrWhiteSpace(prefixMissing))
{
throw new InvalidOperationException(StringUtil.Loc("ArtifactCustomPropertyInvalid", prefixMissing));
}

return propertyBag;
}
catch (JsonException)
{
throw new ArgumentException(StringUtil.Loc("ArtifactCustomPropertiesNotJson", properties));
}
}

private string NormalizeJobIdentifier(string jobIdentifier)
{
jobIdentifier = jobIdentifierRgx.Replace(jobIdentifier, string.Empty).Replace(".default", string.Empty);
Expand Down
2 changes: 2 additions & 0 deletions src/Misc/layoutbin/en-US/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"AllowContainerUserRunDocker": "Allow user '{0}' run any docker command without SUDO.",
"AlreadyConfiguredError": "Cannot configure the agent because it is already configured. To reconfigure the agent, run 'config.cmd remove' or './config.sh remove' first.",
"ArgumentNeeded": "'{0}' has to be specified.",
"ArtifactCustomPropertiesNotJson": "Artifact custom properties is not valid JSON: '{0}'",
"ArtifactCustomPropertyInvalid": "Artifact custom properties must be prefixed with 'user-'. Invalid property: '{0}'",
"ArtifactDownloadFailed": "Failed to download the artifact from {0}.",
"ArtifactLocationRequired": "Artifact location is required.",
"ArtifactNameIsNotValid": "Artifact name is not valid: {0}. It cannot contain '\\', /', \"', ':', '<', '>', '|', '*', and '?'",
Expand Down

0 comments on commit 8ea28eb

Please sign in to comment.