From d91576a7841ad26d15014feb2355e48a12fc1ba3 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 2 Apr 2026 14:04:57 +0200 Subject: [PATCH] Deploy: separate read/write scoped filesystems in IncrementalDeployService IncrementalDeployService accepted a single ScopedFileSystem used for both reading and writing. The deploy commands passed RealRead which lacks AllowedSpecialFolder.Temp, causing ScopedFileSystemException when AwsS3SyncApplyStrategy stages files in /tmp/ for S3 upload. Rather than just swapping RealRead for RealWrite, properly separate the concerns: the service now accepts distinct read and write filesystems, using each for the appropriate operations (plan file reads vs temp dir writes). This also fixes the Plan command which was writing its output file through a read-scoped filesystem. Made-with: Cursor --- .../Deploying/IncrementalDeployService.cs | 14 +++++++------- .../Commands/Assembler/DeployCommands.cs | 7 ++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/services/Elastic.Documentation.Assembler/Deploying/IncrementalDeployService.cs b/src/services/Elastic.Documentation.Assembler/Deploying/IncrementalDeployService.cs index 6167412be..3b4303922 100644 --- a/src/services/Elastic.Documentation.Assembler/Deploying/IncrementalDeployService.cs +++ b/src/services/Elastic.Documentation.Assembler/Deploying/IncrementalDeployService.cs @@ -2,7 +2,6 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using System.IO.Abstractions; using Actions.Core.Services; using Amazon.S3; using Amazon.S3.Transfer; @@ -21,14 +20,15 @@ public class IncrementalDeployService( AssemblyConfiguration assemblyConfiguration, IConfigurationContext configurationContext, ICoreService githubActionsService, - ScopedFileSystem fileSystem + ScopedFileSystem readFileSystem, + ScopedFileSystem writeFileSystem ) : IService { private readonly ILogger _logger = logFactory.CreateLogger(); public async Task Plan(IDiagnosticsCollector collector, string environment, string s3BucketName, string @out, float? deleteThreshold, Cancel ctx) { - var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, environment, collector, fileSystem, fileSystem, null, null); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, environment, collector, readFileSystem, writeFileSystem, null, null); var s3Client = new AmazonS3Client(); var planner = new AwsS3SyncPlanStrategy(logFactory, s3Client, s3BucketName, assembleContext); var plan = await planner.Plan(deleteThreshold, ctx); @@ -51,7 +51,7 @@ public async Task Plan(IDiagnosticsCollector collector, string environment if (!string.IsNullOrEmpty(@out)) { var output = SyncPlan.Serialize(plan); - await using var fileStream = fileSystem.File.Create(@out); + await using var fileStream = writeFileSystem.File.Create(@out); await using var writer = new StreamWriter(fileStream); await writer.WriteAsync(output); _logger.LogInformation("Plan written to {OutputFile}", @out); @@ -62,19 +62,19 @@ public async Task Plan(IDiagnosticsCollector collector, string environment public async Task Apply(IDiagnosticsCollector collector, string environment, string s3BucketName, string planFile, Cancel ctx) { - var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, environment, collector, fileSystem, fileSystem, null, null); + var assembleContext = new AssembleContext(assemblyConfiguration, configurationContext, environment, collector, readFileSystem, writeFileSystem, null, null); var s3Client = new AmazonS3Client(); var transferUtility = new TransferUtility(s3Client, new TransferUtilityConfig { ConcurrentServiceRequests = Environment.ProcessorCount * 2, MinSizeBeforePartUpload = S3EtagCalculator.PartSize }); - if (!fileSystem.File.Exists(planFile)) + if (!readFileSystem.File.Exists(planFile)) { collector.EmitError(planFile, "Plan file does not exist."); return false; } - var planJson = await fileSystem.File.ReadAllTextAsync(planFile, ctx); + var planJson = await readFileSystem.File.ReadAllTextAsync(planFile, ctx); var plan = SyncPlan.Deserialize(planJson); _logger.LogInformation("Remote listing completed: {RemoteListingCompleted}", plan.RemoteListingCompleted); _logger.LogInformation("Total files to sync: {TotalFiles}", plan.TotalSyncRequests); diff --git a/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs b/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs index 6142177e2..ac307d213 100644 --- a/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs +++ b/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs @@ -2,7 +2,6 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using System.IO.Abstractions; using Actions.Core.Services; using ConsoleAppFramework; using Elastic.Documentation.Assembler.Deploying; @@ -33,8 +32,7 @@ public async Task Plan(string environment, string s3BucketName, string @out { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = FileSystemFactory.RealRead; - var service = new IncrementalDeployService(logFactory, assemblyConfiguration, configurationContext, githubActionsService, fs); + var service = new IncrementalDeployService(logFactory, assemblyConfiguration, configurationContext, githubActionsService, FileSystemFactory.RealRead, FileSystemFactory.RealWrite); serviceInvoker.AddCommand(service, (environment, s3BucketName, @out, deleteThreshold), static async (s, collector, state, ctx) => await s.Plan(collector, state.environment, state.s3BucketName, state.@out, state.deleteThreshold, ctx) ); @@ -51,8 +49,7 @@ public async Task Apply(string environment, string s3BucketName, string pla { await using var serviceInvoker = new ServiceInvoker(collector); - var fs = FileSystemFactory.RealWrite; - var service = new IncrementalDeployService(logFactory, assemblyConfiguration, configurationContext, githubActionsService, fs); + var service = new IncrementalDeployService(logFactory, assemblyConfiguration, configurationContext, githubActionsService, FileSystemFactory.RealRead, FileSystemFactory.RealWrite); serviceInvoker.AddCommand(service, (environment, s3BucketName, planFile), static async (s, collector, state, ctx) => await s.Apply(collector, state.environment, state.s3BucketName, state.planFile, ctx) );