From fd505f40c0d11a433b6b1a53da065993bd508de5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 3 Jun 2022 13:37:25 +0200 Subject: [PATCH] scaffolder: fix binary file uploads during dry-run Signed-off-by: Patrik Oldsberg --- .changeset/yellow-falcons-sin.md | 5 +++ .../TemplateEditorPage/DryRunContext.test.tsx | 44 +++++++++++++++++++ .../TemplateEditorPage/DryRunContext.tsx | 26 ++++++++++- 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 .changeset/yellow-falcons-sin.md create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.test.tsx diff --git a/.changeset/yellow-falcons-sin.md b/.changeset/yellow-falcons-sin.md new file mode 100644 index 0000000000..943a93d8ac --- /dev/null +++ b/.changeset/yellow-falcons-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Handle binary files and files that are too large during dry-run content upload. diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.test.tsx new file mode 100644 index 0000000000..184118914a --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { base64EncodeContent } from './DryRunContext'; + +// eslint-disable-next-line no-restricted-imports +import { TextEncoder } from 'util'; + +window.TextEncoder = TextEncoder; + +describe('base64EncodeContent', () => { + it('encodes text files', () => { + expect(base64EncodeContent('abc')).toBe('YWJj'); + expect(base64EncodeContent('abc'.repeat(1000000))).toBe( + btoa(''), + ); + }); + + it('encodes binary files', () => { + expect(base64EncodeContent('\x00\x01\x02')).toBe('AAEC'); + expect(base64EncodeContent('😅')).toBe('8J+YhQ=='); + // Triggers chunking + expect(base64EncodeContent('😅'.repeat(18000))).toBe( + '8J+YhfCfmIXwn5iF8J+YhfCfmIXwn5iF'.repeat(3000), + ); + // Triggers size check + expect(base64EncodeContent('😅'.repeat(1000000))).toBe( + btoa(''), + ); + }); +}); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx index b1fe8a19ff..0a5bfeefd5 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx @@ -29,6 +29,9 @@ import React, { import { scaffolderApiRef } from '../../api'; import { ScaffolderDryRunResponse } from '../../types'; +const MAX_CONTENT_SIZE = 256 * 1024; +const CHUNK_SIZE = 0x8000; + interface DryRunOptions { templateContent: string; values: JsonObject; @@ -54,6 +57,27 @@ interface DryRunProviderProps { children: ReactNode; } +export function base64EncodeContent(content: string): string { + if (content.length > MAX_CONTENT_SIZE) { + return btoa(''); + } + + try { + return btoa(content); + } catch { + const decoder = new TextEncoder(); + const buffer = decoder.encode(content); + + const chunks = new Array(); + for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) { + chunks.push( + String.fromCharCode(...buffer.slice(offset, offset + CHUNK_SIZE)), + ); + } + return btoa(chunks.join('')); + } +} + export function DryRunProvider(props: DryRunProviderProps) { const scaffolderApi = useApi(scaffolderApiRef); @@ -110,7 +134,7 @@ export function DryRunProvider(props: DryRunProviderProps) { secrets: {}, directoryContents: options.files.map(file => ({ path: file.path, - base64Content: btoa(file.content), + base64Content: base64EncodeContent(file.content), })), });