2023-03-03 13:13:27 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.IO ;
2024-05-22 13:17:22 -04:00
using System.Text.Json ;
2023-03-03 13:13:27 -05:00
using System.Threading.Tasks ;
using System.Xml ;
2024-05-22 13:17:22 -04:00
using EpicGames.Core ;
using EpicGames.Horde ;
using EpicGames.Horde.Server ;
2023-05-22 16:05:23 -04:00
using EpicGames.Horde.Storage ;
using EpicGames.Horde.Storage.Nodes ;
2024-01-21 13:52:05 -05:00
using EpicGames.Horde.Tools ;
2024-05-22 13:17:22 -04:00
using Microsoft.Extensions.DependencyInjection ;
using Microsoft.Extensions.Logging ;
2023-03-03 13:13:27 -05:00
#nullable enable
namespace AutomationTool.Tasks
{
/// <summary>
/// Parameters for a DeployTool task
/// </summary>
public class DeployToolTaskParameters
{
/// <summary>
/// Identifier for the tool
/// </summary>
[TaskParameter]
2024-05-21 20:54:09 -04:00
public string Id { get ; set ; } = String . Empty ;
2023-03-03 13:13:27 -05:00
/// <summary>
/// Settings file to use for the deployment. Should be a JSON file containing server name and access token.
/// </summary>
2024-04-17 12:22:42 -04:00
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string Settings { get ; set ; } = String . Empty ;
2023-03-03 13:13:27 -05:00
2023-03-06 14:39:54 -05:00
/// <summary>
/// Version number for the new tool
/// </summary>
[TaskParameter]
2024-05-21 20:54:09 -04:00
public string Version { get ; set ; } = String . Empty ;
2023-03-06 14:39:54 -05:00
2023-03-10 10:37:10 -05:00
/// <summary>
/// Duration over which to roll out the tool, in minutes.
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public int Duration { get ; set ; } = 0 ;
2023-03-10 10:37:10 -05:00
/// <summary>
/// Whether to create the deployment as paused
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public bool Paused { get ; set ; } = false ;
2023-03-10 10:37:10 -05:00
2023-05-26 11:55:13 -04:00
/// <summary>
/// Zip file containing files to upload
/// </summary>
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string? File { get ; set ; } = null ! ;
2023-05-26 11:55:13 -04:00
2023-03-03 13:13:27 -05:00
/// <summary>
2023-05-22 16:05:23 -04:00
/// Directory to upload for the tool
2023-03-03 13:13:27 -05:00
/// </summary>
2023-05-26 11:55:13 -04:00
[TaskParameter(Optional = true)]
2024-05-21 20:54:09 -04:00
public string? Directory { get ; set ; } = null ! ;
2023-03-03 13:13:27 -05:00
}
/// <summary>
/// Deploys a tool update through Horde
/// </summary>
[TaskElement("DeployTool", typeof(DeployToolTaskParameters))]
public class DeployToolTask : SpawnTaskBase
{
class DeploySettings
{
public string Server { get ; set ; } = String . Empty ;
public string? Token { get ; set ; }
}
2023-05-22 16:05:23 -04:00
/// <summary>
/// Options for a new deployment
/// </summary>
class CreateDeploymentRequest
{
public string Version { get ; set ; } = "Unknown" ;
public double? Duration { get ; set ; }
public bool? CreatePaused { get ; set ; }
public string? Node { get ; set ; }
}
2024-05-22 13:17:22 -04:00
readonly DeployToolTaskParameters _parameters ;
2023-03-03 13:13:27 -05:00
/// <summary>
/// Construct a Helm task
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="parameters">Parameters for the task</param>
public DeployToolTask ( DeployToolTaskParameters parameters )
2023-03-03 13:13:27 -05:00
{
2024-05-22 13:17:22 -04:00
_parameters = parameters ;
2023-03-03 13:13:27 -05:00
}
/// <summary>
2024-05-22 13:17:22 -04:00
/// ExecuteAsync the task.
2023-03-03 13:13:27 -05:00
/// </summary>
2024-05-22 13:17:22 -04:00
/// <param name="job">Information about the current job</param>
/// <param name="buildProducts">Set of build products produced by this node.</param>
/// <param name="tagNameToFileSet">Mapping from tag names to the set of files they include</param>
public override async Task ExecuteAsync ( JobContext job , HashSet < FileReference > buildProducts , Dictionary < string , HashSet < FileReference > > tagNameToFileSet )
2023-03-03 13:13:27 -05:00
{
2024-04-17 12:22:42 -04:00
DeploySettings ? settings = null ;
2024-05-22 13:17:22 -04:00
if ( ! String . IsNullOrEmpty ( _parameters . Settings ) )
2023-03-03 13:13:27 -05:00
{
2024-05-22 13:17:22 -04:00
FileReference settingsFile = ResolveFile ( _parameters . Settings ) ;
2024-04-17 12:22:42 -04:00
if ( ! FileReference . Exists ( settingsFile ) )
{
throw new AutomationException ( $"Settings file '{settingsFile}' does not exist" ) ;
}
2023-03-03 13:13:27 -05:00
2024-04-17 12:22:42 -04:00
byte [ ] settingsData = await FileReference . ReadAllBytesAsync ( settingsFile ) ;
JsonSerializerOptions jsonOptions = new JsonSerializerOptions { AllowTrailingCommas = true , ReadCommentHandling = JsonCommentHandling . Skip , PropertyNameCaseInsensitive = true } ;
2023-03-03 13:13:27 -05:00
2024-04-17 12:22:42 -04:00
settings = JsonSerializer . Deserialize < DeploySettings > ( settingsData , jsonOptions ) ;
if ( settings = = null )
{
throw new AutomationException ( $"Unable to read settings file {settingsFile}" ) ;
}
else if ( settings . Server = = null )
{
throw new AutomationException ( $"Missing 'server' key from {settingsFile}" ) ;
}
2023-03-03 13:13:27 -05:00
}
2024-05-22 13:17:22 -04:00
ToolId toolId = new ToolId ( _parameters . Id ) ;
2024-02-03 12:59:37 -05:00
2024-01-21 13:52:05 -05:00
ServiceCollection serviceCollection = new ServiceCollection ( ) ;
serviceCollection . Configure < HordeOptions > ( options = >
2023-03-03 13:13:27 -05:00
{
2024-04-17 12:22:42 -04:00
if ( ! String . IsNullOrEmpty ( settings ? . Server ) )
{
options . ServerUrl = new Uri ( settings . Server ) ;
}
if ( ! String . IsNullOrEmpty ( settings ? . Token ) )
{
options . AccessToken = settings . Token ;
}
2024-01-21 13:52:05 -05:00
} ) ;
serviceCollection . AddHttpClient ( ) ;
serviceCollection . AddHorde ( ) ;
await using ServiceProvider serviceProvider = serviceCollection . BuildServiceProvider ( ) ;
IHordeClient hordeClient = serviceProvider . GetRequiredService < IHordeClient > ( ) ;
using HordeHttpClient hordeHttpClient = hordeClient . CreateHttpClient ( ) ;
GetServerInfoResponse infoResponse = await hordeHttpClient . GetServerInfoAsync ( ) ;
2024-04-17 12:22:42 -04:00
Logger . LogInformation ( "Uploading {ToolId} to {ServerUrl} (Version: {Version}, API v{ApiVersion})..." , toolId , hordeClient . ServerUrl , infoResponse . ServerVersion , ( int ) infoResponse . ApiVersion ) ;
2024-01-21 13:52:05 -05:00
BlobSerializerOptions serializerOptions = BlobSerializerOptions . Create ( infoResponse . ApiVersion ) ;
2023-05-22 16:05:23 -04:00
2024-07-25 15:09:26 -04:00
IHashedBlobRef handle ;
2023-05-22 16:05:23 -04:00
2024-09-10 10:26:02 -04:00
IStorageNamespace storageNamespace = hordeClient . GetStorageNamespace ( toolId ) ;
await using ( IBlobWriter blobWriter = storageNamespace . CreateBlobWriter ( serializerOptions : serializerOptions ) )
2023-05-22 16:05:23 -04:00
{
DirectoryNode sandbox = new DirectoryNode ( ) ;
2024-05-22 13:17:22 -04:00
if ( _parameters . File ! = null )
2023-05-26 11:55:13 -04:00
{
2024-05-22 13:17:22 -04:00
using FileStream stream = FileReference . Open ( ResolveFile ( _parameters . File ) , FileMode . Open , FileAccess . Read ) ;
2024-01-27 19:24:46 -05:00
await sandbox . CopyFromZipStreamAsync ( stream , blobWriter , new ChunkingOptions ( ) ) ;
2023-05-26 11:55:13 -04:00
}
2024-05-22 13:17:22 -04:00
else if ( _parameters . Directory ! = null )
2023-05-26 11:55:13 -04:00
{
2024-05-22 13:17:22 -04:00
DirectoryInfo directoryInfo = ResolveDirectory ( _parameters . Directory ) . ToDirectoryInfo ( ) ;
2024-01-27 19:24:46 -05:00
await sandbox . AddFilesAsync ( directoryInfo , blobWriter ) ;
2023-05-26 11:55:13 -04:00
}
else
{
throw new AutomationException ( "Either File=... or Directory=... must be specified" ) ;
}
2024-01-27 19:24:46 -05:00
handle = await blobWriter . WriteBlobAsync ( sandbox ) ;
await blobWriter . FlushAsync ( ) ;
2023-05-22 16:05:23 -04:00
}
2024-01-21 13:52:05 -05:00
double? duration = null ;
2024-05-22 13:17:22 -04:00
if ( _parameters . Duration ! = 0 )
2023-05-22 16:05:23 -04:00
{
2024-05-22 13:17:22 -04:00
duration = _parameters . Duration ;
2023-05-22 16:05:23 -04:00
}
2024-01-21 13:52:05 -05:00
bool? createPaused = null ;
2024-05-22 13:17:22 -04:00
if ( _parameters . Paused )
2023-05-22 16:05:23 -04:00
{
2024-01-21 13:52:05 -05:00
createPaused = true ;
2023-05-22 16:05:23 -04:00
}
2024-07-25 15:09:26 -04:00
HashedBlobRefValue locator = handle . GetRefValue ( ) ;
2024-05-22 13:17:22 -04:00
ToolDeploymentId deploymentId = await hordeHttpClient . CreateToolDeploymentAsync ( toolId , _parameters . Version , duration , createPaused , locator ) ;
2024-01-21 13:52:05 -05:00
Logger . LogInformation ( "Created {ToolId} deployment {DeploymentId}" , toolId , deploymentId ) ;
2023-03-03 13:13:27 -05:00
}
/// <summary>
/// Output this task out to an XML writer.
/// </summary>
2024-05-22 13:17:22 -04:00
public override void Write ( XmlWriter writer )
2023-03-03 13:13:27 -05:00
{
2024-05-22 13:17:22 -04:00
Write ( writer , _parameters ) ;
2023-03-03 13:13:27 -05:00
}
/// <summary>
/// Find all the tags which are used as inputs to this task
/// </summary>
/// <returns>The tag names which are read by this task</returns>
public override IEnumerable < string > FindConsumedTagNames ( )
{
yield break ;
}
/// <summary>
/// Find all the tags which are modified by this task
/// </summary>
/// <returns>The tag names which are modified by this task</returns>
public override IEnumerable < string > FindProducedTagNames ( )
{
yield break ;
}
}
}