Skip to content

Commit

Permalink
[APM] Add support to upload source map as string or file (elastic#105443
Browse files Browse the repository at this point in the history
)

* adding support to upload a sourcemap

* addressing PR comments

* addressing PR comments
  • Loading branch information
cauemarcondes authored and kibanamachine committed Jul 16, 2021
1 parent 001d980 commit cc11565
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 5 deletions.
5 changes: 2 additions & 3 deletions x-pack/plugins/apm/server/lib/fleet/source_maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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';
Expand All @@ -23,7 +22,7 @@ export interface ApmArtifactBody {
serviceName: string;
serviceVersion: string;
bundleFilepath: string;
sourceMap: t.TypeOf<typeof sourceMapRt>;
sourceMap: SourceMap;
}
export type ArtifactSourceMap = Omit<Artifact, 'body'> & {
body: ApmArtifactBody;
Expand Down
10 changes: 8 additions & 2 deletions x-pack/plugins/apm/server/routes/source_maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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({
Expand All @@ -32,6 +33,8 @@ export const sourceMapRt = t.intersection([
}),
]);

export type SourceMap = t.TypeOf<typeof sourceMapRt>;

const listSourceMapRoute = createApmServerRoute({
endpoint: 'GET /api/apm/sourcemaps',
options: { tags: ['access:apm'] },
Expand Down Expand Up @@ -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 }) => {
Expand Down
51 changes: 51 additions & 0 deletions x-pack/plugins/apm/server/utils/string_from_buffer_rt.test.ts
Original file line number Diff line number Diff line change
@@ -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 <Router>\n <div>\n <ul>\n <li>\n <Link to="/">Home</Link>\n </li>\n <li>\n <Link to="/about">About</Link>\n </li>\n <li>\n <Link to="/dashboard">Dashboard</Link>\n </li>\n <li>\n <Link to="/error">Error</Link>\n </li>\n </ul>\n\n <hr />\n\n {/*\n A <Switch> looks through all its children <Route>\n elements and renders the first one whose path\n matches the current URL. Use a <Switch> any time\n you have multiple routes, but you want only one\n of them to render at a time\n */}\n <Switch>\n <Route exact path="/">\n <Home />\n </Route>\n <Route path="/about">\n <About />\n </Route>\n <Route path="/dashboard">\n <Dashboard />\n </Route>\n <Route path="/error">\n <ErrorPage />\n </Route>\n </Switch>\n </div>\n </Router>\n );\n}\n\n// You can think of these components as "pages"\n// in your app.\n\nfunction Home() {\n return (\n <div>\n <h2>HOME</h2>\n </div>\n );\n}\n\nfunction About() {\n return (\n <div>\n <h2>about</h2>\n </div>\n );\n}\n\nfunction Dashboard() {\n return (\n <div>\n <h2>Dashboard</h2>\n </div>\n );\n}\n\nfunction ErrorPage() {\n throw new Error(\'Boomm\')\n return (\n <div>\n <h2>error</h2>\n </div>\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 <React.StrictMode>\n <App />\n </React.StrictMode>,\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)));
});
});
});
20 changes: 20 additions & 0 deletions x-pack/plugins/apm/server/utils/string_from_buffer_rt.ts
Original file line number Diff line number Diff line change
@@ -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<string, Buffer, unknown>(
'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);
}
);

0 comments on commit cc11565

Please sign in to comment.