-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
create cli-pull-ot command to sync assessments from OneTrust to disk #375
Changes from all commits
d5c4c11
c91e6c6
ce3b7dc
d1ece52
0a35092
f29068a
8233efd
a398525
1f0c2cc
8abbc4a
1aa6857
1a3090e
99e8d82
25a73a8
52c8e7a
5e1dc0d
2f99e72
1dcc90e
a811105
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
#!/usr/bin/env node | ||
import { logger } from './logger'; | ||
import colors from 'colors'; | ||
import { | ||
getListOfAssessments, | ||
getAssessment, | ||
writeOneTrustAssessment, | ||
parseCliPullOtArguments, | ||
createOneTrustGotInstance, | ||
} from './oneTrust'; | ||
import { OneTrustPullResource } from './enums'; | ||
import { mapSeries } from 'bluebird'; | ||
|
||
/** | ||
* Pull configuration from OneTrust down locally to disk | ||
* | ||
* Dev Usage: | ||
* yarn ts-node ./src/cli-pull-ot.ts --hostname=customer.my.onetrust.com --auth=$ONE_TRUST_OAUTH_TOKEN --file=./oneTrustAssessment.json | ||
* | ||
* Standard usage | ||
* yarn cli-pull-ot --hostname=customer.my.onetrust.com --auth=$ONE_TRUST_OAUTH_TOKEN --file=./oneTrustAssessment.json | ||
*/ | ||
async function main(): Promise<void> { | ||
const { file, fileFormat, hostname, auth, resource, debug } = | ||
parseCliPullOtArguments(); | ||
|
||
try { | ||
if (resource === OneTrustPullResource.Assessments) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: best to move these into helper functions so that they can be imported as code if needed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will do in a follow up PR! |
||
// use the hostname and auth token to instantiate a client to talk to OneTrust | ||
const oneTrust = createOneTrustGotInstance({ hostname, auth }); | ||
|
||
// fetch the list of all assessments in the OneTrust organization | ||
const assessments = await getListOfAssessments({ oneTrust }); | ||
|
||
// fetch details about one assessment at a time and sync to disk right away to avoid running out of memory | ||
await mapSeries(assessments, async (assessment, index) => { | ||
logger.info( | ||
`Fetching details about assessment ${index + 1} of ${ | ||
assessments.length | ||
}...`, | ||
); | ||
const assessmentDetails = await getAssessment({ | ||
oneTrust, | ||
assessmentId: assessment.assessmentId, | ||
}); | ||
|
||
writeOneTrustAssessment({ | ||
assessment, | ||
assessmentDetails, | ||
index, | ||
total: assessments.length, | ||
file, | ||
fileFormat, | ||
}); | ||
}); | ||
} | ||
} catch (err) { | ||
logger.error( | ||
colors.red( | ||
`An error occurred pulling the resource ${resource} from OneTrust: ${ | ||
debug ? err.stack : err.message | ||
}`, | ||
), | ||
); | ||
process.exit(1); | ||
} | ||
|
||
// Indicate success | ||
logger.info( | ||
colors.green( | ||
`Successfully synced OneTrust ${resource} to disk at "${file}"!`, | ||
), | ||
); | ||
} | ||
|
||
main(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import got, { Got } from 'got'; | ||
|
||
/** | ||
* Instantiate an instance of got that is capable of making requests to OneTrust | ||
* | ||
* @param param - information about the OneTrust URL | ||
* @returns The instance of got that is capable of making requests to the customer ingress | ||
*/ | ||
export const createOneTrustGotInstance = ({ | ||
hostname, | ||
auth, | ||
}: { | ||
/** Hostname of the OneTrust API */ | ||
hostname: string; | ||
/** The OAuth access token */ | ||
auth: string; | ||
}): Got => | ||
got.extend({ | ||
prefixUrl: `https://${hostname}`, | ||
headers: { | ||
accept: 'application/json', | ||
'content-type': 'application/json', | ||
authorization: `Bearer ${auth}`, | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Got } from 'got'; | ||
import { OneTrustGetAssessmentResponse } from './types'; | ||
|
||
/** | ||
* Retrieve details about a particular assessment. | ||
* | ||
* @param param - the information about the OneTrust client and assessment to retrieve | ||
* @returns details about the assessment | ||
*/ | ||
export const getAssessment = async ({ | ||
oneTrust, | ||
assessmentId, | ||
}: { | ||
/** The OneTrust client instance */ | ||
oneTrust: Got; | ||
/** The ID of the assessment to retrieve */ | ||
assessmentId: string; | ||
}): Promise<OneTrustGetAssessmentResponse> => { | ||
const { body } = await oneTrust.get( | ||
`api/assessment/v2/assessments/${assessmentId}/export?ExcludeSkippedQuestions=false`, | ||
); | ||
|
||
return JSON.parse(body) as OneTrustGetAssessmentResponse; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: should use an io-ts codec to validate here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Created the codecs here https://github.com/transcend-io/privacy-types/pull/205/files and battle-tested them agains the OneTrust API responses. Caught lots of stuff! Thanks for the suggestion. |
||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { Got } from 'got'; | ||
import { logger } from '../logger'; | ||
import { | ||
OneTrustAssessment, | ||
OneTrustGetListOfAssessmentsResponse, | ||
} from './types'; | ||
|
||
/** | ||
* Fetch a list of all assessments from the OneTrust client. | ||
* | ||
* @param param - the information about the OneTrust client | ||
* @returns a list of OneTrustAssessment | ||
*/ | ||
export const getListOfAssessments = async ({ | ||
oneTrust, | ||
}: { | ||
/** The OneTrust client instance */ | ||
oneTrust: Got; | ||
}): Promise<OneTrustAssessment[]> => { | ||
let currentPage = 0; | ||
let totalPages = 1; | ||
let totalElements = 0; | ||
|
||
const allAssessments: OneTrustAssessment[] = []; | ||
|
||
logger.info('Getting list of all assessments from OneTrust...'); | ||
while (currentPage < totalPages) { | ||
// eslint-disable-next-line no-await-in-loop | ||
const { body } = await oneTrust.get( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit sjould use io-ts codec validation here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will do in a follow up PR as well! |
||
`api/assessment/v2/assessments?page=${currentPage}&size=2000`, | ||
); | ||
const { page, content } = JSON.parse( | ||
body, | ||
) as OneTrustGetListOfAssessmentsResponse; | ||
allAssessments.push(...(content ?? [])); | ||
if (currentPage === 0) { | ||
totalPages = page?.totalPages ?? 0; | ||
totalElements = page?.totalElements ?? 0; | ||
} | ||
currentPage += 1; | ||
|
||
// log progress | ||
logger.info( | ||
`Fetched ${allAssessments.length} of ${totalElements} assessments.`, | ||
); | ||
} | ||
|
||
return allAssessments; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export * from './getListOfAssessments'; | ||
export * from './createOneTrustGotInstance'; | ||
export * from './getAssessment'; | ||
export * from './writeOneTrustAssessment'; | ||
export * from './parseCliPullOtArguments'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you do with the file after its pulled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now we do nothing, but I am going to create another command that accepts the file path as input and then pushes it to Transcend via the importOneTrustAssessmentForms mutation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it, maybe use the same command but have a
--dryRun
parameter or something?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or have it dump into the transcend.yml shape and use tr-push to push up (would need to add assessment support to that)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That could work as well! So if dryRun = true we push to transcend. Otherwise, we save it as a file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was thinking opposite, default behavior is to pull from OT and push to transcend. probaly would make sense to rename to
tr-sync-ot
then if you specify
--dryRun=true
this would only pull from OT and write to file instead of syncing to transcendThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense! I'll do that.