dd-trace 2.12.2 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +2 -0
- package/ext/tags.d.ts +2 -1
- package/ext/tags.js +2 -1
- package/index.d.ts +43 -20
- package/package.json +5 -3
- package/packages/datadog-core/src/storage/async_resource.js +19 -1
- package/packages/datadog-instrumentations/index.js +1 -52
- package/packages/datadog-instrumentations/src/crypto.js +30 -0
- package/packages/datadog-instrumentations/src/cucumber.js +15 -0
- package/packages/datadog-instrumentations/src/fs.js +11 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +70 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +5 -34
- package/packages/datadog-instrumentations/src/helpers/instrumentations.js +7 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +59 -0
- package/packages/datadog-instrumentations/src/http/server.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +33 -11
- package/packages/datadog-instrumentations/src/net.js +13 -0
- package/packages/datadog-plugin-cucumber/src/index.js +4 -0
- package/packages/datadog-plugin-fs/src/index.js +72 -38
- package/packages/datadog-plugin-jest/src/index.js +25 -4
- package/packages/datadog-plugin-mocha/src/index.js +2 -2
- package/packages/datadog-plugin-mongodb-core/src/index.js +32 -8
- package/packages/datadog-plugin-oracledb/src/index.js +12 -4
- package/packages/dd-trace/index.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +3 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/index.js +20 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +48 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
- package/packages/dd-trace/src/appsec/iast/iast-context.js +50 -0
- package/packages/dd-trace/src/appsec/iast/index.js +59 -0
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +94 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +70 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +113 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +50 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +53 -8
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +23 -24
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +220 -0
- package/packages/dd-trace/src/config.js +89 -10
- package/packages/dd-trace/src/constants.js +9 -1
- package/packages/dd-trace/src/encode/0.4.js +51 -58
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +13 -34
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +84 -0
- package/packages/dd-trace/src/encode/span-stats.js +155 -0
- package/packages/dd-trace/src/exporters/agent/index.js +25 -7
- package/packages/dd-trace/src/exporters/agent/writer.js +7 -4
- package/packages/dd-trace/src/{profiling/exporters → exporters/common}/form-data.js +0 -0
- package/packages/dd-trace/src/exporters/common/request.js +25 -10
- package/packages/dd-trace/src/exporters/common/writer.js +9 -6
- package/packages/dd-trace/src/exporters/span-stats/index.js +20 -0
- package/packages/dd-trace/src/exporters/span-stats/writer.js +54 -0
- package/packages/dd-trace/src/format.js +2 -0
- package/packages/dd-trace/src/id.js +16 -13
- package/packages/dd-trace/src/iitm.js +11 -0
- package/packages/dd-trace/src/index.js +10 -0
- package/packages/dd-trace/src/noop/proxy.js +87 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +77 -6
- package/packages/dd-trace/src/opentracing/tracer.js +1 -1
- package/packages/dd-trace/src/plugin_manager.js +107 -65
- package/packages/dd-trace/src/plugins/index.js +58 -45
- package/packages/dd-trace/src/plugins/log_plugin.js +16 -9
- package/packages/dd-trace/src/plugins/util/ci.js +34 -9
- package/packages/dd-trace/src/plugins/util/git.js +52 -2
- package/packages/dd-trace/src/plugins/util/ip_blocklist.js +25 -0
- package/packages/dd-trace/src/plugins/util/tags.js +4 -1
- package/packages/dd-trace/src/plugins/util/web.js +99 -2
- package/packages/dd-trace/src/priority_sampler.js +36 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/proxy.js +23 -89
- package/packages/dd-trace/src/ritm.js +10 -1
- package/packages/dd-trace/src/span_processor.js +7 -1
- package/packages/dd-trace/src/span_stats.js +210 -0
- package/packages/dd-trace/src/startup-log.js +8 -19
- package/packages/dd-trace/src/telemetry/dependencies.js +83 -0
- package/packages/dd-trace/src/{telemetry.js → telemetry/index.js} +11 -79
- package/packages/dd-trace/src/telemetry/send-data.js +35 -0
- package/scripts/install_plugin_modules.js +17 -26
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const OVERHEAD_CONTROLLER_CONTEXT_KEY = 'oce'
|
|
4
|
+
const REPORT_VULNERABILITY = 'REPORT_VULNERABILITY'
|
|
5
|
+
|
|
6
|
+
const GLOBAL_OCE_CONTEXT = {}
|
|
7
|
+
let config = {}
|
|
8
|
+
let availableRequest = 0
|
|
9
|
+
const OPERATIONS = {
|
|
10
|
+
REPORT_VULNERABILITY: {
|
|
11
|
+
hasQuota: (context) => {
|
|
12
|
+
const reserved = context && context.tokens && context.tokens[REPORT_VULNERABILITY] > 0
|
|
13
|
+
if (reserved) {
|
|
14
|
+
context.tokens[REPORT_VULNERABILITY]--
|
|
15
|
+
}
|
|
16
|
+
return reserved
|
|
17
|
+
},
|
|
18
|
+
name: REPORT_VULNERABILITY,
|
|
19
|
+
initialTokenBucketSize () {
|
|
20
|
+
return typeof config.maxContextOperations === 'number' ? config.maxContextOperations : 2
|
|
21
|
+
},
|
|
22
|
+
initContext: function (context) {
|
|
23
|
+
context.tokens[REPORT_VULNERABILITY] = this.initialTokenBucketSize()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function _getNewContext () {
|
|
29
|
+
const oceContext = {
|
|
30
|
+
tokens: {}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const operation in OPERATIONS) {
|
|
34
|
+
OPERATIONS[operation].initContext(oceContext)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return oceContext
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _getContext (iastContext) {
|
|
41
|
+
if (iastContext && iastContext[OVERHEAD_CONTROLLER_CONTEXT_KEY]) {
|
|
42
|
+
return iastContext[OVERHEAD_CONTROLLER_CONTEXT_KEY]
|
|
43
|
+
}
|
|
44
|
+
return GLOBAL_OCE_CONTEXT
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _resetGlobalContext () {
|
|
48
|
+
Object.assign(GLOBAL_OCE_CONTEXT, _getNewContext())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function acquireRequest (rootSpan) {
|
|
52
|
+
if (availableRequest > 0) {
|
|
53
|
+
const sampling = config && typeof config.requestSampling === 'number'
|
|
54
|
+
? config.requestSampling : 30
|
|
55
|
+
if (rootSpan.context().toSpanId().slice(-2) <= sampling) {
|
|
56
|
+
availableRequest--
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function releaseRequest () {
|
|
64
|
+
if (availableRequest < config.maxConcurrentRequests) {
|
|
65
|
+
availableRequest++
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hasQuota (operation, iastContext) {
|
|
70
|
+
const oceContext = _getContext(iastContext)
|
|
71
|
+
return operation.hasQuota(oceContext)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function initializeRequestContext (iastContext) {
|
|
75
|
+
if (iastContext) iastContext[OVERHEAD_CONTROLLER_CONTEXT_KEY] = _getNewContext()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function configure (cfg) {
|
|
79
|
+
config = cfg
|
|
80
|
+
availableRequest = config.maxConcurrentRequests
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_resetGlobalContext()
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
OVERHEAD_CONTROLLER_CONTEXT_KEY,
|
|
87
|
+
OPERATIONS,
|
|
88
|
+
_resetGlobalContext,
|
|
89
|
+
initializeRequestContext,
|
|
90
|
+
hasQuota,
|
|
91
|
+
acquireRequest,
|
|
92
|
+
releaseRequest,
|
|
93
|
+
configure
|
|
94
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const pathLine = {
|
|
3
|
+
getFirstNonDDPathAndLine,
|
|
4
|
+
getFirstNonDDPathAndLineFromCallsites, // Exported only for test purposes
|
|
5
|
+
calculateDDBasePath, // Exported only for test purposes
|
|
6
|
+
ddBasePath: calculateDDBasePath(__dirname) // Only for test purposes
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const EXCLUDED_PATHS = [
|
|
10
|
+
'/node_modules/diagnostics_channel'
|
|
11
|
+
]
|
|
12
|
+
const EXCLUDED_PATH_PREFIXES = [
|
|
13
|
+
'node:diagnostics_channel',
|
|
14
|
+
'diagnostics_channel'
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
function calculateDDBasePath (dirname) {
|
|
18
|
+
const dirSteps = dirname.split(path.sep)
|
|
19
|
+
const packagesIndex = dirSteps.indexOf('packages')
|
|
20
|
+
return dirSteps.slice(0, packagesIndex).join(path.sep) + path.sep
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getCallSiteInfo () {
|
|
24
|
+
const previousPrepareStackTrace = Error.prepareStackTrace
|
|
25
|
+
let callsiteList
|
|
26
|
+
Error.prepareStackTrace = function (_, callsites) {
|
|
27
|
+
callsiteList = callsites
|
|
28
|
+
}
|
|
29
|
+
const e = new Error()
|
|
30
|
+
e.stack
|
|
31
|
+
Error.prepareStackTrace = previousPrepareStackTrace
|
|
32
|
+
return callsiteList
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getFirstNonDDPathAndLineFromCallsites (callsites) {
|
|
36
|
+
if (callsites) {
|
|
37
|
+
for (let i = 0; i < callsites.length; i++) {
|
|
38
|
+
const callsite = callsites[i]
|
|
39
|
+
const path = callsite.getFileName()
|
|
40
|
+
if (!isExcluded(callsite) && path.indexOf(pathLine.ddBasePath) === -1) {
|
|
41
|
+
return {
|
|
42
|
+
path,
|
|
43
|
+
line: callsite.getLineNumber()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isExcluded (callsite) {
|
|
52
|
+
if (callsite.isNative()) return true
|
|
53
|
+
const filename = callsite.getFileName()
|
|
54
|
+
for (let i = 0; i < EXCLUDED_PATHS.length; i++) {
|
|
55
|
+
if (filename.indexOf(EXCLUDED_PATHS[i]) > -1) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (let i = 0; i < EXCLUDED_PATH_PREFIXES.length; i++) {
|
|
60
|
+
if (filename.indexOf(EXCLUDED_PATH_PREFIXES[i]) === 0) {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getFirstNonDDPathAndLine () {
|
|
68
|
+
return getFirstNonDDPathAndLineFromCallsites(getCallSiteInfo())
|
|
69
|
+
}
|
|
70
|
+
module.exports = pathLine
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { MANUAL_KEEP } = require('../../../../../ext/tags')
|
|
2
|
+
const VULNERABILITIES_KEY = 'vulnerabilities'
|
|
3
|
+
const IAST_JSON_TAG_KEY = '_dd.iast.json'
|
|
4
|
+
|
|
5
|
+
function createVulnerability (type, evidence, spanId, location) {
|
|
6
|
+
if (type && evidence && spanId) {
|
|
7
|
+
return {
|
|
8
|
+
type,
|
|
9
|
+
evidence,
|
|
10
|
+
location: {
|
|
11
|
+
spanId,
|
|
12
|
+
...location
|
|
13
|
+
},
|
|
14
|
+
hash: createHash(type, location)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createHash (type, location) {
|
|
21
|
+
let hashSource
|
|
22
|
+
if (location) {
|
|
23
|
+
hashSource = `${type}:${location.path}:${location.line}`
|
|
24
|
+
} else {
|
|
25
|
+
hashSource = type
|
|
26
|
+
}
|
|
27
|
+
let hash = 0
|
|
28
|
+
let offset = 0
|
|
29
|
+
const size = hashSource.length
|
|
30
|
+
for (let i = 0; i < size; i++) {
|
|
31
|
+
hash = ((hash << 5) - hash) + hashSource.charCodeAt(offset++)
|
|
32
|
+
}
|
|
33
|
+
return hash
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function addVulnerability (iastContext, vulnerability) {
|
|
37
|
+
if (iastContext && vulnerability && vulnerability.evidence && vulnerability.type &&
|
|
38
|
+
vulnerability.location && vulnerability.location.spanId) {
|
|
39
|
+
iastContext[VULNERABILITIES_KEY] = iastContext[VULNERABILITIES_KEY] || []
|
|
40
|
+
iastContext[VULNERABILITIES_KEY].push(vulnerability)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isValidVulnerability (vulnerability) {
|
|
45
|
+
return vulnerability && vulnerability.type &&
|
|
46
|
+
vulnerability.evidence && vulnerability.evidence.value &&
|
|
47
|
+
vulnerability.location && vulnerability.location.spanId
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function jsonVulnerabilityFromVulnerability (vulnerability) {
|
|
51
|
+
const jsonVulnerability = {
|
|
52
|
+
type: vulnerability.type,
|
|
53
|
+
hash: vulnerability.hash,
|
|
54
|
+
evidence: {
|
|
55
|
+
value: vulnerability.evidence.value
|
|
56
|
+
},
|
|
57
|
+
location: {
|
|
58
|
+
spanId: vulnerability.location.spanId
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (vulnerability.location.path) {
|
|
62
|
+
jsonVulnerability.location.path = vulnerability.location.path
|
|
63
|
+
}
|
|
64
|
+
if (vulnerability.location.line) {
|
|
65
|
+
jsonVulnerability.location.line = vulnerability.location.line
|
|
66
|
+
}
|
|
67
|
+
return jsonVulnerability
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function sendVulnerabilities (iastContext) {
|
|
71
|
+
if (iastContext && iastContext.rootSpan && iastContext[VULNERABILITIES_KEY] &&
|
|
72
|
+
iastContext[VULNERABILITIES_KEY].length && iastContext.rootSpan.addTags) {
|
|
73
|
+
const span = iastContext.rootSpan
|
|
74
|
+
const allVulnerabilities = iastContext[VULNERABILITIES_KEY]
|
|
75
|
+
// TODO support sources and ranges
|
|
76
|
+
const jsonToSend = {
|
|
77
|
+
vulnerabilities: []
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
deduplicateVulnerabilities(allVulnerabilities).forEach((vulnerability) => {
|
|
81
|
+
if (isValidVulnerability(vulnerability)) {
|
|
82
|
+
jsonToSend.vulnerabilities.push(jsonVulnerabilityFromVulnerability(vulnerability))
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
if (jsonToSend.vulnerabilities.length > 0) {
|
|
87
|
+
const tags = {}
|
|
88
|
+
// TODO: Store this outside of the span and set the tag in the exporter.
|
|
89
|
+
tags[IAST_JSON_TAG_KEY] = JSON.stringify(jsonToSend)
|
|
90
|
+
tags[MANUAL_KEEP] = 'true'
|
|
91
|
+
span.addTags(tags)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return IAST_JSON_TAG_KEY
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function deduplicateVulnerabilities (vulnerabilities) {
|
|
98
|
+
const uniqueVulnerabilities = new Set()
|
|
99
|
+
return vulnerabilities.filter((vulnerability) => {
|
|
100
|
+
const key = `${vulnerability.type}${vulnerability.hash}`
|
|
101
|
+
if (!uniqueVulnerabilities.has(key)) {
|
|
102
|
+
uniqueVulnerabilities.add(key)
|
|
103
|
+
return true
|
|
104
|
+
}
|
|
105
|
+
return false
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
createVulnerability,
|
|
111
|
+
addVulnerability,
|
|
112
|
+
sendVulnerabilities
|
|
113
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const request = require('../../../exporters/common/request')
|
|
3
|
+
const log = require('../../../log')
|
|
4
|
+
|
|
5
|
+
const { CoverageCIVisibilityEncoder } = require('../../../encode/coverage-ci-visibility')
|
|
6
|
+
const BaseWriter = require('../../../exporters/common/writer')
|
|
7
|
+
|
|
8
|
+
function safeJSONStringify (value) {
|
|
9
|
+
return JSON.stringify(value, (key, value) =>
|
|
10
|
+
key !== 'dd-api-key' ? value : undefined
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class Writer extends BaseWriter {
|
|
15
|
+
constructor ({ url }) {
|
|
16
|
+
super(...arguments)
|
|
17
|
+
this._url = url
|
|
18
|
+
this._encoder = new CoverageCIVisibilityEncoder()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_sendPayload (form, _, done) {
|
|
22
|
+
const options = {
|
|
23
|
+
path: '/api/v2/citestcov',
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY,
|
|
27
|
+
...form.getHeaders()
|
|
28
|
+
},
|
|
29
|
+
timeout: 15000
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
options.protocol = this._url.protocol
|
|
33
|
+
options.hostname = this._url.hostname
|
|
34
|
+
options.port = this._url.port
|
|
35
|
+
|
|
36
|
+
log.debug(() => `Request to the intake: ${safeJSONStringify(options)}`)
|
|
37
|
+
|
|
38
|
+
request(form, options, (err, res) => {
|
|
39
|
+
if (err) {
|
|
40
|
+
log.error(err)
|
|
41
|
+
done()
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
log.debug(`Response from the intake: ${res}`)
|
|
45
|
+
done()
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = Writer
|
|
@@ -2,30 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
const URL = require('url').URL
|
|
4
4
|
const Writer = require('./writer')
|
|
5
|
-
const
|
|
5
|
+
const CoverageWriter = require('./coverage-writer')
|
|
6
|
+
|
|
7
|
+
const log = require('../../../log')
|
|
6
8
|
|
|
7
9
|
class AgentlessCiVisibilityExporter {
|
|
8
10
|
constructor (config) {
|
|
9
|
-
|
|
11
|
+
this._config = config
|
|
12
|
+
const { tags, site, url, isIntelligentTestRunnerEnabled } = config
|
|
13
|
+
this._isIntelligentTestRunnerEnabled = isIntelligentTestRunnerEnabled
|
|
10
14
|
this._url = url || new URL(`https://citestcycle-intake.${site}`)
|
|
11
15
|
this._writer = new Writer({ url: this._url, tags })
|
|
16
|
+
this._timer = undefined
|
|
17
|
+
this._coverageTimer = undefined
|
|
18
|
+
|
|
19
|
+
this._coverageUrl = url || new URL(`https://event-platform-intake.${site}`)
|
|
20
|
+
this._coverageWriter = new CoverageWriter({ url: this._coverageUrl })
|
|
21
|
+
|
|
22
|
+
process.once('beforeExit', () => {
|
|
23
|
+
this._writer.flush()
|
|
24
|
+
this._coverageWriter.flush()
|
|
25
|
+
})
|
|
26
|
+
}
|
|
12
27
|
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
exportCoverage ({ testSpan, coverageFiles }) {
|
|
29
|
+
const formattedCoverage = {
|
|
30
|
+
traceId: testSpan.context()._traceId,
|
|
31
|
+
spanId: testSpan.context()._spanId,
|
|
32
|
+
files: coverageFiles
|
|
33
|
+
}
|
|
34
|
+
this._coverageWriter.append(formattedCoverage)
|
|
35
|
+
|
|
36
|
+
const { flushInterval } = this._config
|
|
37
|
+
|
|
38
|
+
if (flushInterval === 0) {
|
|
39
|
+
this._coverageWriter.flush()
|
|
40
|
+
} else if (flushInterval > 0 && !this._coverageTimer) {
|
|
41
|
+
this._coverageTimer = setTimeout(() => {
|
|
42
|
+
this._coverageWriter.flush()
|
|
43
|
+
this._coverageTimer = clearTimeout(this._coverageTimer)
|
|
44
|
+
}, flushInterval).unref()
|
|
15
45
|
}
|
|
16
|
-
this._scheduler && this._scheduler.start()
|
|
17
46
|
}
|
|
18
47
|
|
|
19
48
|
export (trace) {
|
|
20
49
|
this._writer.append(trace)
|
|
21
50
|
|
|
22
|
-
|
|
51
|
+
const { flushInterval } = this._config
|
|
52
|
+
|
|
53
|
+
if (flushInterval === 0) {
|
|
23
54
|
this._writer.flush()
|
|
55
|
+
} else if (flushInterval > 0 && !this._timer) {
|
|
56
|
+
this._timer = setTimeout(() => {
|
|
57
|
+
this._writer.flush()
|
|
58
|
+
this._timer = clearTimeout(this._timer)
|
|
59
|
+
}, flushInterval).unref()
|
|
24
60
|
}
|
|
25
61
|
}
|
|
26
62
|
|
|
27
|
-
|
|
28
|
-
|
|
63
|
+
setUrl (url, coverageUrl = url) {
|
|
64
|
+
try {
|
|
65
|
+
url = new URL(url)
|
|
66
|
+
coverageUrl = new URL(coverageUrl)
|
|
67
|
+
this._url = url
|
|
68
|
+
this._coverageUrl = coverageUrl
|
|
69
|
+
this._writer.setUrl(url)
|
|
70
|
+
this._coverageWriter.setUrl(coverageUrl)
|
|
71
|
+
} catch (e) {
|
|
72
|
+
log.error(e)
|
|
73
|
+
}
|
|
29
74
|
}
|
|
30
75
|
}
|
|
31
76
|
|
|
@@ -5,16 +5,37 @@ const log = require('../../../log')
|
|
|
5
5
|
const { AgentlessCiVisibilityEncoder } = require('../../../encode/agentless-ci-visibility')
|
|
6
6
|
const BaseWriter = require('../../../exporters/common/writer')
|
|
7
7
|
|
|
8
|
+
function safeJSONStringify (value) {
|
|
9
|
+
return JSON.stringify(value, (key, value) =>
|
|
10
|
+
key !== 'dd-api-key' ? value : undefined
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
class Writer extends BaseWriter {
|
|
9
15
|
constructor ({ url, tags }) {
|
|
10
16
|
super(...arguments)
|
|
11
17
|
const { 'runtime-id': runtimeId, env, service } = tags
|
|
12
18
|
this._url = url
|
|
13
|
-
this._encoder = new AgentlessCiVisibilityEncoder({ runtimeId, env, service })
|
|
19
|
+
this._encoder = new AgentlessCiVisibilityEncoder(this, { runtimeId, env, service })
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
_sendPayload (data, _, done) {
|
|
17
|
-
|
|
23
|
+
const options = {
|
|
24
|
+
path: '/api/v2/citestcycle',
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY,
|
|
28
|
+
'Content-Type': 'application/msgpack'
|
|
29
|
+
},
|
|
30
|
+
timeout: 15000
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
options.protocol = this._url.protocol
|
|
34
|
+
options.hostname = this._url.hostname
|
|
35
|
+
options.port = this._url.port
|
|
36
|
+
|
|
37
|
+
log.debug(() => `Request to the intake: ${safeJSONStringify(options)}`)
|
|
38
|
+
request(data, options, (err, res) => {
|
|
18
39
|
if (err) {
|
|
19
40
|
log.error(err)
|
|
20
41
|
done()
|
|
@@ -26,26 +47,4 @@ class Writer extends BaseWriter {
|
|
|
26
47
|
}
|
|
27
48
|
}
|
|
28
49
|
|
|
29
|
-
function makeRequest (data, url, cb) {
|
|
30
|
-
const options = {
|
|
31
|
-
path: '/api/v2/citestcycle',
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers: {
|
|
34
|
-
'Content-Type': 'application/msgpack',
|
|
35
|
-
'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY
|
|
36
|
-
},
|
|
37
|
-
timeout: 15000
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
options.protocol = url.protocol
|
|
41
|
-
options.hostname = url.hostname
|
|
42
|
-
options.port = url.port
|
|
43
|
-
|
|
44
|
-
log.debug(() => `Request to the intake: ${JSON.stringify(options)}`)
|
|
45
|
-
|
|
46
|
-
request(data, options, false, (err, res) => {
|
|
47
|
-
cb(err, res)
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
50
|
module.exports = Writer
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const https = require('https')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
const FormData = require('../../../exporters/common/form-data')
|
|
7
|
+
|
|
8
|
+
const log = require('../../../log')
|
|
9
|
+
const {
|
|
10
|
+
getLatestCommits,
|
|
11
|
+
getRepositoryUrl,
|
|
12
|
+
generatePackFilesForCommits,
|
|
13
|
+
getCommitsToUpload
|
|
14
|
+
} = require('../../../plugins/util/git')
|
|
15
|
+
|
|
16
|
+
const isValidSha = (sha) => /[0-9a-f]{40}/.test(sha)
|
|
17
|
+
|
|
18
|
+
function sanitizeCommits (commits) {
|
|
19
|
+
return commits.map(({ id: commitSha, type }) => {
|
|
20
|
+
if (type !== 'commit') {
|
|
21
|
+
throw new Error('Invalid commit response')
|
|
22
|
+
}
|
|
23
|
+
const sanitizedCommit = commitSha.replace(/[^0-9a-f]+/g, '')
|
|
24
|
+
if (sanitizedCommit !== commitSha || !isValidSha(sanitizedCommit)) {
|
|
25
|
+
throw new Error('Invalid commit format')
|
|
26
|
+
}
|
|
27
|
+
return sanitizedCommit
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getCommonRequestOptions (url) {
|
|
32
|
+
return {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY
|
|
36
|
+
},
|
|
37
|
+
timeout: 15000,
|
|
38
|
+
protocol: url.protocol,
|
|
39
|
+
hostname: url.hostname,
|
|
40
|
+
port: url.port
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* This function posts the SHAs of the commits of the last month
|
|
46
|
+
* The response are the commits for which the backend already has information
|
|
47
|
+
* This response is used to know which commits can be ignored from there on
|
|
48
|
+
*/
|
|
49
|
+
function getCommitsToExclude ({ url, repositoryUrl }, callback) {
|
|
50
|
+
const latestCommits = getLatestCommits()
|
|
51
|
+
const [headCommit] = latestCommits
|
|
52
|
+
|
|
53
|
+
const commonOptions = getCommonRequestOptions(url)
|
|
54
|
+
|
|
55
|
+
const options = {
|
|
56
|
+
...commonOptions,
|
|
57
|
+
headers: {
|
|
58
|
+
...commonOptions.headers,
|
|
59
|
+
'Content-Type': 'application/json'
|
|
60
|
+
},
|
|
61
|
+
path: '/api/v2/git/repository/search_commits'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const localCommitData = JSON.stringify({
|
|
65
|
+
meta: {
|
|
66
|
+
repository_url: repositoryUrl
|
|
67
|
+
},
|
|
68
|
+
data: latestCommits.map(commit => ({
|
|
69
|
+
id: commit,
|
|
70
|
+
type: 'commit'
|
|
71
|
+
}))
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const request = https.request(options, (res) => {
|
|
75
|
+
let responseData = ''
|
|
76
|
+
|
|
77
|
+
res.on('data', chunk => { responseData += chunk })
|
|
78
|
+
res.on('end', () => {
|
|
79
|
+
if (res.statusCode === 200) {
|
|
80
|
+
let commitsToExclude
|
|
81
|
+
try {
|
|
82
|
+
commitsToExclude = sanitizeCommits(JSON.parse(responseData).data)
|
|
83
|
+
} catch (e) {
|
|
84
|
+
callback(new Error(`Can't parse response: ${e.message}`))
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
callback(null, commitsToExclude, headCommit)
|
|
88
|
+
} else {
|
|
89
|
+
const error = new Error(`Error getting commits: ${res.statusCode} ${res.statusMessage}`)
|
|
90
|
+
callback(error)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
request.write(localCommitData)
|
|
96
|
+
request.on('error', callback)
|
|
97
|
+
|
|
98
|
+
request.end()
|
|
99
|
+
|
|
100
|
+
return request
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* This function uploads a git packfile
|
|
105
|
+
*/
|
|
106
|
+
function uploadPackFile ({ url, packFileToUpload, repositoryUrl, headCommit }, callback) {
|
|
107
|
+
const form = new FormData()
|
|
108
|
+
|
|
109
|
+
const pushedSha = JSON.stringify({
|
|
110
|
+
data: {
|
|
111
|
+
id: headCommit,
|
|
112
|
+
type: 'commit'
|
|
113
|
+
},
|
|
114
|
+
meta: {
|
|
115
|
+
repository_url: repositoryUrl
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
form.append('pushedSha', pushedSha, { contentType: 'application/json' })
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const packFileContent = fs.readFileSync(packFileToUpload)
|
|
123
|
+
// The original filename includes a random prefix, so we remove it here
|
|
124
|
+
const [, filename] = path.basename(packFileToUpload).split('-')
|
|
125
|
+
form.append('packfile', packFileContent, {
|
|
126
|
+
filename,
|
|
127
|
+
contentType: 'application/octet-stream'
|
|
128
|
+
})
|
|
129
|
+
} catch (e) {
|
|
130
|
+
callback(new Error(`Error reading packfile: ${packFileToUpload}`))
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const commonOptions = getCommonRequestOptions(url)
|
|
135
|
+
|
|
136
|
+
const options = {
|
|
137
|
+
...commonOptions,
|
|
138
|
+
path: '/api/v2/git/repository/packfile',
|
|
139
|
+
headers: {
|
|
140
|
+
...commonOptions.headers,
|
|
141
|
+
...form.getHeaders()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const req = https.request(options, res => {
|
|
146
|
+
res.on('data', () => {})
|
|
147
|
+
res.on('end', () => {
|
|
148
|
+
if (res.statusCode === 204) {
|
|
149
|
+
callback(null)
|
|
150
|
+
} else {
|
|
151
|
+
const error = new Error(`Error uploading packfiles: ${res.statusCode} ${res.statusMessage}`)
|
|
152
|
+
error.status = res.statusCode
|
|
153
|
+
|
|
154
|
+
callback(error)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
req.on('error', err => {
|
|
160
|
+
callback(err)
|
|
161
|
+
})
|
|
162
|
+
form.pipe(req)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* This function uploads git metadata to CI Visibility's backend.
|
|
167
|
+
*/
|
|
168
|
+
function sendGitMetadata (site, callback) {
|
|
169
|
+
const url = new URL(`https://api.${site}`)
|
|
170
|
+
|
|
171
|
+
const repositoryUrl = getRepositoryUrl()
|
|
172
|
+
|
|
173
|
+
getCommitsToExclude({ url, repositoryUrl }, (err, commitsToExclude, headCommit) => {
|
|
174
|
+
if (err) {
|
|
175
|
+
callback(err)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
const commitsToUpload = getCommitsToUpload(commitsToExclude)
|
|
179
|
+
|
|
180
|
+
if (!commitsToUpload.length) {
|
|
181
|
+
log.debug('No commits to upload')
|
|
182
|
+
callback(null)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const packFilesToUpload = generatePackFilesForCommits(commitsToUpload)
|
|
187
|
+
|
|
188
|
+
let packFileIndex = 0
|
|
189
|
+
// This uploads packfiles sequentially
|
|
190
|
+
const uploadPackFileCallback = (err) => {
|
|
191
|
+
if (err || packFileIndex === packFilesToUpload.length) {
|
|
192
|
+
callback(err)
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
return uploadPackFile(
|
|
196
|
+
{
|
|
197
|
+
packFileToUpload: packFilesToUpload[packFileIndex++],
|
|
198
|
+
url,
|
|
199
|
+
repositoryUrl,
|
|
200
|
+
headCommit
|
|
201
|
+
},
|
|
202
|
+
uploadPackFileCallback
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
uploadPackFile(
|
|
207
|
+
{
|
|
208
|
+
url,
|
|
209
|
+
packFileToUpload: packFilesToUpload[packFileIndex++],
|
|
210
|
+
repositoryUrl,
|
|
211
|
+
headCommit
|
|
212
|
+
},
|
|
213
|
+
uploadPackFileCallback
|
|
214
|
+
)
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
sendGitMetadata
|
|
220
|
+
}
|