Skip to content

Commit

Permalink
Merge pull request #37 from microsoft/getpackagesoperation
Browse files Browse the repository at this point in the history
Add new GetPackages operation
  • Loading branch information
Seann-Murdock authored Dec 1, 2023
2 parents 52dfa88 + 3bd128b commit bae4b7c
Show file tree
Hide file tree
Showing 18 changed files with 356 additions and 17 deletions.
134 changes: 134 additions & 0 deletions .azurePipelines/build-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
trigger: none

parameters:
- name: Version
default: '1.0.0'

pool:
vmImage: windows-latest

variables:
- name: projectName
value: "PackageUploader"
- name: buildConfiguration
value: "release"
- name: esrpConnectedServiceName
value: "ESRP CodeSigning External"
- name: version
value: ${{ parameters.Version }}

stages:
- stage: Build
displayName:

jobs:
- job: BuildWindows
displayName: Build win-x64

variables:
- name: buildRuntime
value: "win-x64"

steps:
- task: DotNetCoreCLI@2
displayName: 'dotnet publish'
inputs:
command: 'publish'
publishWebProjects: false
projects: './src/PackageUploader.Application/PackageUploader.Application.csproj'
arguments: '--self-contained -o $(Build.BinariesDirectory) -p:Version=$(version) -r $(buildRuntime) -c $(buildConfiguration)'
zipAfterPublish: false
modifyOutputPath: false

- task: EsrpCodeSigning@3
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
displayName: 'Code sign binary files'
inputs:
ConnectedServiceName: $(esrpConnectedServiceName)
FolderPath: '$(Build.BinariesDirectory)'
Pattern: '*.exe'
signConfigType: 'inlineSignParams'
inlineOperation: |
[
{
"KeyCode": "CP-230012",
"OperationCode": "SigntoolSign",
"ToolName": "sign",
"ToolVersion": "1.0",
"Parameters": {
"OpusName": "Microsoft",
"OpusInfo": "https://www.microsoft.com",
"FileDigest": "/fd SHA256",
"PageHash": "/NPH",
"TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
}
},
{
"KeyCode": "CP-230012",
"OperationCode": "SigntoolVerify",
"ToolName": "sign",
"ToolVersion": "1.0",
"Parameters": {}
}
]
SessionTimeout: '60'
MaxConcurrency: '50'
MaxRetryAttempts: '5'

- task: PowerShell@2
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
displayName: 'Remove signing result file'
inputs:
targetType: 'inline'
script: 'Remove-Item * -Include *.md'
workingDirectory: '$(Build.BinariesDirectory)'

- task: ArchiveFiles@2
displayName: 'Zip files to Staging directory'
inputs:
rootFolderOrFile: '$(Build.BinariesDirectory)'
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/$(projectName).$(version).$(buildRuntime).zip'
replaceExistingArchive: true

- task: PublishPipelineArtifact@1
displayName: 'publish artifact to pipeline'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifact: '$(projectName).$(buildRuntime)'
publishLocation: 'pipeline'

- job: BuildLinux
displayName: Build linux-x64

variables:
- name: buildRuntime
value: "linux-x64"

steps:
- task: DotNetCoreCLI@2
displayName: 'dotnet publish'
inputs:
command: 'publish'
publishWebProjects: false
projects: './src/PackageUploader.Application/PackageUploader.Application.csproj'
arguments: '--self-contained -o $(Build.BinariesDirectory) -p:Version=$(version) -r $(buildRuntime) -c $(buildConfiguration)'
zipAfterPublish: false
modifyOutputPath: false

- task: ArchiveFiles@2
displayName: 'Zip files to Staging directory'
inputs:
rootFolderOrFile: '$(Build.BinariesDirectory)'
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/$(projectName).$(version).$(buildRuntime).zip'
replaceExistingArchive: true

- task: PublishPipelineArtifact@1
displayName: 'publish artifact to pipeline'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifact: '$(projectName).$(buildRuntime)'
publishLocation: 'pipeline'
17 changes: 17 additions & 0 deletions Operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@
- **productId**: *productId* or *bigId* required
- **bigId**: *productId* or *bigId* required

# GetPackages
###### Returns list of game packages from a branch
#### Config file ([template](https://github.com/microsoft/GameStoreBroker/blob/main/templates/GetPackages.json))
##### Definition:
- **operationName**: "GetPackages",
- **aadAuthInfo**: required when using authentication method *AppCert* or *AppSecret*
- **tenantId**: required
- **clientId**: required
- **certificateThumbprint**: required when using authentication method *AppCert*
- **certificateStore**: optional when using authentication method *AppCert* (default *My*)
- **certificateLocation**: optional when using authentication method *AppCert* (default *CurrentUser*)
- **productId**: *productId* or *bigId* required
- **bigId**: *productId* or *bigId* required
- **branchFriendlyName**: *flightName* or *branchFriendlyName* required
- **flightName**: *flightName* or *branchFriendlyName* required
- **marketGroupName**: optional - if not set, it will use *default* as the market group (case sensitive)

# UploadUwpPackage
###### Uploads Uwp game package
#### Config file ([template](https://github.com/microsoft/GameStoreBroker/blob/main/templates/UploadUwpPackage.json))
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Running the package uploader requires three arguments.
| Operation | Description |
| --- | ---|
| **[GetProduct](https://github.com/microsoft/PackageUploader/blob/main/Operations.md#GetProduct)** | Gets metadata for the product. This is useful for getting the productId, BigId, product name that's used in all configuration files. And a list of the BranchFriendlyNames and FlightNames of the product. |
| **[GetPackages](https://github.com/microsoft/PackageUploader/blob/main/Operations.md#GetPackages)** | Gets a list of the packages in a branch or flight. |
| **[UploadUwpPackage](https://github.com/microsoft/PackageUploader/blob/main/Operations.md#UploadUwpPackage)** | Uploads a UWP game package. |
| **[UploadXvcPackage](https://github.com/microsoft/PackageUploader/blob/main/Operations.md#UploadXvcPackage)** | Uploads a XVC game package and assets, including EKB, SubVal, and layout files. |
| **[RemovePackages](https://github.com/microsoft/PackageUploader/blob/main/Operations.md#RemovePackages)** | Removes game packages and assets from a branch. We recommend keeping only your 10 most recent packages to ensure optimal performance. |
Expand All @@ -143,6 +144,7 @@ For more information on operation parameters, see [Operations](https://github.co
| **-s, --ClientSecret <ClientSecret>** | The client secret of the Azure AD application (only for AppSecret) |
| **-a, --Authentication <AppCert\|AppSecret\|Browser\|Default>** | The authentication method (default: AppSecret) |
| **-v, --Verbose** | Log verbose messages, such as HTTP calls |
| **-d, --Data** | Do not log on console and only return data (only for Get operations) |
| **-l, --LogFile <LogFile>** | The location of the log file |
| **-?, -h, --help** | Show Help and usage information |

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace PackageUploader.Application.Config;

internal class GetPackagesOperationConfig : PackageBranchOperationConfig
{
internal override string GetOperationName() => "GetPackages";

public string MarketGroupName { get; set; } = "default";

protected override void Validate(IList<ValidationResult> validationResults)
{
if (string.IsNullOrWhiteSpace(MarketGroupName))
{
MarketGroupName = "default";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ public static Option<T> Required<T>(this Option<T> option, bool required = true)
return option;
}

public static void AddOperation<T1, T2>(this IServiceCollection services, HostBuilderContext context) where T1 : Operation where T2 : BaseOperationConfig
public static IServiceCollection AddOperation<T1, T2>(this IServiceCollection services, HostBuilderContext context) where T1 : Operation where T2 : BaseOperationConfig
{
services.AddScoped<T1>();
services.AddOptions<T2>().Bind(context.Configuration).ValidateDataAnnotations();
return services;
}

public static IConfigurationBuilder AddConfigFile(this IConfigurationBuilder builder, FileInfo configFile, Program.ConfigFileFormat configFileFormat) =>
Expand Down
31 changes: 31 additions & 0 deletions src/PackageUploader.Application/Models/Package.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using PackageUploader.ClientApi.Client.Ingestion.Models;

namespace PackageUploader.Application.Models;

public class Package
{
/// <summary>
/// If the package is certified
/// </summary>
public bool? IsCertified { get; set; }

/// <summary>
/// File name of the package
/// </summary>
public string FileName { get; set; }

/// <summary>
/// File size of the package
/// </summary>
public long? FileSize { get; set; }

public Package(GamePackage gamePackage)
{
IsCertified = gamePackage.IsCertified;
FileName = gamePackage.FileName;
FileSize = gamePackage.FileSize;
}
}
66 changes: 66 additions & 0 deletions src/PackageUploader.Application/Operations/GetPackagesOperation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PackageUploader.Application.Config;
using PackageUploader.Application.Extensions;
using PackageUploader.Application.Models;
using PackageUploader.ClientApi;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;

namespace PackageUploader.Application.Operations;

internal class GetPackagesOperation : Operation
{
private readonly IPackageUploaderService _storeBrokerService;
private readonly ILogger<GetPackagesOperation> _logger;
private readonly GetPackagesOperationConfig _config;

public GetPackagesOperation(IPackageUploaderService storeBrokerService, ILogger<GetPackagesOperation> logger, IOptions<GetPackagesOperationConfig> config) : base(logger)
{
_storeBrokerService = storeBrokerService ?? throw new ArgumentNullException(nameof(storeBrokerService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_config = config?.Value ?? throw new ArgumentNullException(nameof(config));
}

protected override async Task ProcessAsync(CancellationToken ct)
{
_logger.LogInformation("Starting {operationName} operation.", _config.GetOperationName());

var product = await _storeBrokerService.GetProductAsync(_config, ct).ConfigureAwait(false);
var packageBranch = await _storeBrokerService.GetGamePackageBranch(product, _config, ct).ConfigureAwait(false);
var packages = await _storeBrokerService.GetGamePackagesAsync(product, packageBranch, _config.MarketGroupName, ct)
.Select(gamePackage => new Package(gamePackage))
.ToListAsync(ct).ConfigureAwait(false);

var packagesJson = PackagesToJson(packages);
_logger.LogInformation("Packages:");
Console.WriteLine(packagesJson);
}

private static readonly JsonSerializerOptions DefaultJsonSerializerOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};

public static string PackagesToJson(IEnumerable<Package> packages)
{
try
{
var serializedObject = JsonSerializer.Serialize(packages, DefaultJsonSerializerOptions);
return serializedObject;
}
catch (Exception ex)
{
return $"Could not serialize packages to json - {ex.Message}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ protected override async Task ProcessAsync(CancellationToken ct)

var product = new Product(gameProduct, gamePackageBranches);

_logger.LogInformation("Product: {product}", product.ToJson());
var productJson = product.ToJson();
_logger.LogInformation("Product:");
Console.WriteLine(productJson);
}
}
19 changes: 16 additions & 3 deletions src/PackageUploader.Application/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ internal class Program
private const string LogTimestampFormat = "yyyy-MM-dd HH:mm:ss.fff ";

// Options
private static readonly Option<bool> DataOption = new (new[] { "-d", "--Data" }, "Do not log on console and only return data");
private static readonly Option<bool> VerboseOption = new (new[] { "-v", "--Verbose" }, "Log verbose messages such as http calls");
private static readonly Option<FileInfo> LogFileOption = new(new[] { "-l", "--LogFile" }, "The location of the log file");
private static readonly Option<string> ClientSecretOption = new (new[] { "-s", "--ClientSecret" }, "The client secret of the AAD app (only for AppSecret)");
Expand All @@ -53,9 +54,12 @@ private static async Task<int> Main(string[] args)
private static void ConfigureLogging(HostBuilderContext context, ILoggingBuilder logging)
{
var invocationContext = context.GetInvocationContext();
var isData = invocationContext.GetOptionValue(DataOption);
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Warning);
logging.AddFilter("PackageUploader", invocationContext.GetOptionValue(VerboseOption) ? LogLevel.Trace : LogLevel.Information);
logging.SetMinimumLevel(LogLevel.Error);
logging.AddFilter("PackageUploader",
isData ? LogLevel.Error :
invocationContext.GetOptionValue(VerboseOption) ? LogLevel.Trace : LogLevel.Information);
logging.AddFilter<FileLoggerProvider>("PackageUploader", LogLevel.Trace);
logging.AddSimpleFile(options =>
{
Expand All @@ -67,6 +71,10 @@ private static void ConfigureLogging(HostBuilderContext context, ILoggingBuilder
file.Path = logFile?.FullName ?? Path.Combine(Path.GetTempPath(), $"PackageUploader_{DateTime.Now:yyyyMMddHHmmss}.log");
file.Append = true;
});
if (isData)
{
logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Error);
}
logging.AddSimpleConsole(options =>
{
options.SingleLine = true;
Expand All @@ -87,6 +95,7 @@ private static void ConfigureServices(HostBuilderContext context, IServiceCollec
services.AddOperation<RemovePackagesOperation, RemovePackagesOperationConfig>(context);
services.AddOperation<ImportPackagesOperation, ImportPackagesOperationConfig>(context);
services.AddOperation<PublishPackagesOperation, PublishPackagesOperationConfig>(context);
services.AddOperation<GetPackagesOperation, GetPackagesOperationConfig>(context);
}

private static void ConfigureAppConfiguration(HostBuilderContext context, IConfigurationBuilder builder, string[] args)
Expand Down Expand Up @@ -114,7 +123,7 @@ private static CommandLineBuilder BuildCommandLine()
{
new Command("GetProduct", "Gets metadata of the product")
{
ConfigFileOption, ConfigFileFormatOption, ClientSecretOption, AuthenticationMethodOption
ConfigFileOption, ConfigFileFormatOption, ClientSecretOption, AuthenticationMethodOption, DataOption
}.AddOperationHandler<GetProductOperation>(),
new Command("UploadUwpPackage", "Uploads Uwp game package")
{
Expand All @@ -136,6 +145,10 @@ private static CommandLineBuilder BuildCommandLine()
{
ConfigFileOption, ConfigFileFormatOption, ClientSecretOption, AuthenticationMethodOption
}.AddOperationHandler<PublishPackagesOperation>(),
new Command("GetPackages", "Gets the list of packages from a branch or flight")
{
ConfigFileOption, ConfigFileFormatOption, ClientSecretOption, AuthenticationMethodOption, DataOption
}.AddOperationHandler<GetPackagesOperation>(),
};
rootCommand.AddGlobalOption(VerboseOption);
rootCommand.AddGlobalOption(LogFileOption);
Expand Down
Loading

0 comments on commit bae4b7c

Please sign in to comment.