diff --git a/x-pack/plugins/apm/server/lib/fleet/source_maps.ts b/x-pack/plugins/apm/server/lib/fleet/source_maps.ts index 6d608f7751f3ba..10514ddcbdf620 100644 --- a/x-pack/plugins/apm/server/lib/fleet/source_maps.ts +++ b/x-pack/plugins/apm/server/lib/fleet/source_maps.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import * as t from 'io-ts'; import { CoreSetup, CoreStart, @@ -14,7 +13,7 @@ import { import { promisify } from 'util'; import { unzip } from 'zlib'; import { Artifact } from '../../../../fleet/server'; -import { sourceMapRt } from '../../routes/source_maps'; +import { SourceMap } from '../../routes/source_maps'; import { APMPluginStartDependencies } from '../../types'; import { getApmPackgePolicies } from './get_apm_package_policies'; import { APM_SERVER, PackagePolicy } from './register_fleet_policy_callbacks'; @@ -23,7 +22,7 @@ export interface ApmArtifactBody { serviceName: string; serviceVersion: string; bundleFilepath: string; - sourceMap: t.TypeOf; + sourceMap: SourceMap; } export type ArtifactSourceMap = Omit & { body: ApmArtifactBody; diff --git a/x-pack/plugins/apm/server/routes/source_maps.ts b/x-pack/plugins/apm/server/routes/source_maps.ts index f6d160e68a76af..d92bad31cd8d8f 100644 --- a/x-pack/plugins/apm/server/routes/source_maps.ts +++ b/x-pack/plugins/apm/server/routes/source_maps.ts @@ -5,9 +5,9 @@ * 2.0. */ import Boom from '@hapi/boom'; -import { jsonRt } from '@kbn/io-ts-utils'; import * as t from 'io-ts'; import { SavedObjectsClientContract } from 'kibana/server'; +import { jsonRt } from '@kbn/io-ts-utils'; import { createApmArtifact, deleteApmArtifact, @@ -17,6 +17,7 @@ import { import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client'; import { createApmServerRoute } from './create_apm_server_route'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { stringFromBufferRt } from '../utils/string_from_buffer_rt'; export const sourceMapRt = t.intersection([ t.type({ @@ -32,6 +33,8 @@ export const sourceMapRt = t.intersection([ }), ]); +export type SourceMap = t.TypeOf; + const listSourceMapRoute = createApmServerRoute({ endpoint: 'GET /api/apm/sourcemaps', options: { tags: ['access:apm'] }, @@ -62,7 +65,10 @@ const uploadSourceMapRoute = createApmServerRoute({ service_name: t.string, service_version: t.string, bundle_filepath: t.string, - sourcemap: jsonRt.pipe(sourceMapRt), + sourcemap: t + .union([t.string, stringFromBufferRt]) + .pipe(jsonRt) + .pipe(sourceMapRt), }), }), handler: async ({ params, plugins, core }) => { diff --git a/x-pack/plugins/apm/server/utils/string_from_buffer_rt.test.ts b/x-pack/plugins/apm/server/utils/string_from_buffer_rt.test.ts new file mode 100644 index 00000000000000..4e21215cac8b5d --- /dev/null +++ b/x-pack/plugins/apm/server/utils/string_from_buffer_rt.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isRight } from 'fp-ts/lib/Either'; +import { stringFromBufferRt } from './string_from_buffer_rt'; + +const sourceMap = { + version: 3, + file: 'static/js/main.chunk.js', + sources: [ + '/foo/src/index.css', + '/foo/src/App.js', + 'webpack:///./src/index.css?bb0a', + '/foo/src/index.js', + '/foo/src/reportWebVitals.js', + ], + sourcesContent: [ + "// Imports\nimport ___CSS_LOADER_API_IMPORT___ from \"../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(true);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"body {\\n margin: 0;\\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\\n sans-serif;\\n -webkit-font-smoothing: antialiased;\\n -moz-osx-font-smoothing: grayscale;\\n}\\n\\ncode {\\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\\n monospace;\\n}\\n\", \"\",{\"version\":3,\"sources\":[\"webpack://src/index.css\"],\"names\":[],\"mappings\":\"AAAA;EACE,SAAS;EACT;;cAEY;EACZ,mCAAmC;EACnC,kCAAkC;AACpC;;AAEA;EACE;aACW;AACb\",\"sourcesContent\":[\"body {\\n margin: 0;\\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\\n sans-serif;\\n -webkit-font-smoothing: antialiased;\\n -moz-osx-font-smoothing: grayscale;\\n}\\n\\ncode {\\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\\n monospace;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n", + 'import React from "react";\nimport {\n BrowserRouter as Router,\n Switch,\n Route,\n Link\n} from "react-router-dom";\n\n// This site has 3 pages, all of which are rendered\n// dynamically in the browser (not server rendered).\n//\n// Although the page does not ever refresh, notice how\n// React Router keeps the URL up to date as you navigate\n// through the site. This preserves the browser history,\n// making sure things like the back button and bookmarks\n// work properly.\n\nexport default function App() {\n return (\n \n
\n
    \n
  • \n Home\n
  • \n
  • \n About\n
  • \n
  • \n Dashboard\n
  • \n
  • \n Error\n
  • \n
\n\n
\n\n {/*\n A looks through all its children \n elements and renders the first one whose path\n matches the current URL. Use a any time\n you have multiple routes, but you want only one\n of them to render at a time\n */}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n
\n );\n}\n\n// You can think of these components as "pages"\n// in your app.\n\nfunction Home() {\n return (\n
\n

HOME

\n
\n );\n}\n\nfunction About() {\n return (\n
\n

about

\n
\n );\n}\n\nfunction Dashboard() {\n return (\n
\n

Dashboard

\n
\n );\n}\n\nfunction ErrorPage() {\n throw new Error(\'Boomm\')\n return (\n
\n

error

\n
\n );\n}\n', + "var api = require(\"!../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n var content = require(\"!!../node_modules/css-loader/dist/cjs.js??ref--5-oneOf-4-1!../node_modules/postcss-loader/src/index.js??postcss!./index.css\");\n\n content = content.__esModule ? content.default : content;\n\n if (typeof content === 'string') {\n content = [[module.id, content, '']];\n }\n\nvar options = {};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\nif (module.hot) {\n if (!content.locals || module.hot.invalidate) {\n var isEqualLocals = function isEqualLocals(a, b, isNamedExport) {\n if (!a && b || a && !b) {\n return false;\n }\n\n var p;\n\n for (p in a) {\n if (isNamedExport && p === 'default') {\n // eslint-disable-next-line no-continue\n continue;\n }\n\n if (a[p] !== b[p]) {\n return false;\n }\n }\n\n for (p in b) {\n if (isNamedExport && p === 'default') {\n // eslint-disable-next-line no-continue\n continue;\n }\n\n if (!a[p]) {\n return false;\n }\n }\n\n return true;\n};\n var oldLocals = content.locals;\n\n module.hot.accept(\n \"!!../node_modules/css-loader/dist/cjs.js??ref--5-oneOf-4-1!../node_modules/postcss-loader/src/index.js??postcss!./index.css\",\n function () {\n content = require(\"!!../node_modules/css-loader/dist/cjs.js??ref--5-oneOf-4-1!../node_modules/postcss-loader/src/index.js??postcss!./index.css\");\n\n content = content.__esModule ? content.default : content;\n\n if (typeof content === 'string') {\n content = [[module.id, content, '']];\n }\n\n if (!isEqualLocals(oldLocals, content.locals)) {\n module.hot.invalidate();\n\n return;\n }\n\n oldLocals = content.locals;\n\n update(content);\n }\n )\n }\n\n module.hot.dispose(function() {\n update();\n });\n}\n\nmodule.exports = content.locals || {};", + "/*eslint-disable import/first */\nimport { init as initApm } from '@elastic/apm-rum'\ninitApm({\n serviceName: 'fleet-source-map-client',\n serverUrl: 'http://localhost:8200',\n // serverUrl: 'https://776d64ec093b47ff86c752f62baa8f51.apm.us-west1.gcp.cloud.es.io:443',\n serviceVersion: '1.0.0',\n environment: 'production'\n})\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n", + "const reportWebVitals = onPerfEntry => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n", + ], + mappings: + ';;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;ACNA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAVA;AAAA;AAAA;AAAA;AAAA;AAeA;AAAA;AAAA;AAAA;AASA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AAAA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AAAA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AAAA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAVA;AAAA;AAAA;AAAA;AAAA;AAzBA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AA2CA;AAGA;AACA;AAjDA;AACA;AAiDA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAIA;AACA;AAPA;AACA;AAOA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAIA;AACA;AAPA;AACA;AAOA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAIA;AACA;AAPA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAIA;AACA;AARA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5FA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACjFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAOA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC1BA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;A', + sourceRoot: '', +}; + +describe('stringFromBufferRt', () => { + describe('decode', () => { + it('converts from buffer to string', () => { + const sourceMapBuffer = Buffer.from(JSON.stringify(sourceMap)); + const decoded = stringFromBufferRt.decode(sourceMapBuffer); + if (isRight(decoded)) { + expect(decoded.right).toEqual(JSON.stringify(sourceMap)); + } else { + expect(true).toBeFalsy(); + } + }); + }); + describe('encode', () => { + it('converts from string to buffer', () => { + const encoded = stringFromBufferRt.encode(JSON.stringify(sourceMap)); + expect(encoded).toEqual(Buffer.from(JSON.stringify(sourceMap))); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/utils/string_from_buffer_rt.ts b/x-pack/plugins/apm/server/utils/string_from_buffer_rt.ts new file mode 100644 index 00000000000000..3e9304361c8636 --- /dev/null +++ b/x-pack/plugins/apm/server/utils/string_from_buffer_rt.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import * as t from 'io-ts'; + +export const stringFromBufferRt = new t.Type( + 'stringFromBufferRt', + t.string.is, + (input, context) => { + return Buffer.isBuffer(input) + ? t.success(input.toString('utf-8')) + : t.failure(input, context, 'Input is not a Buffer'); + }, + (str) => { + return Buffer.from(str); + } +);