Add lint capabilities and a workflow for lint

This commit is contained in:
Javier de Pedro López 2020-12-10 18:30:29 +01:00
Родитель f1c552a616
Коммит 7fd1e5dc95
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: F43A23D4F89CCC6F
8 изменённых файлов: 2059 добавлений и 168 удалений

24
.github/workflows/node-lint.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,24 @@
on:
push
name: Lint ghec-audit-log-cli
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 13.x]
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install Dependencies
run: npm run lint

Просмотреть файл

@ -1,75 +1,74 @@
#! /usr/bin/env node
const YAML = require('yaml');
const fs = require('fs');
const {graphql} = require('@octokit/graphql');
const {requestEntries} = require('./ghec-audit-log-client');
const {validateInput} = require('./ghec-audit-log-utils');
const YAML = require('yaml')
const fs = require('fs')
const { graphql } = require('@octokit/graphql')
const { requestEntries } = require('./ghec-audit-log-client')
const { validateInput } = require('./ghec-audit-log-utils')
// Obtain configuration
const { program } = require('commander');
program.version('1.0.0', '-v, --version', 'Output the current version')
const { program } = require('commander')
program.version('1.0.0', '-v, --version', 'Output the current version')
.option('-t, --token <string>', 'the token to access the API (mandatory)')
.option('-o, --org <string>', 'the organization we want to extract the audit log from')
.option('-cfg, --config <string>', 'location for the config yaml file. Default ".ghec-audit-log"', './.ghec-audit-log')
.option('-p, --pretty', 'prints the json data in a readable format', false)
.option('-l, --limit <number>', 'a maximum limit on the number of items retrieved')
.option('-f, --file <string>', 'the output file where the result should be printed')
.option('-c, --cursor <string>', 'if provided, this cursor will be used to query the newest entries from the cursor provided. If not present, the result will contain all the audit log from the org');
.option('-c, --cursor <string>', 'if provided, this cursor will be used to query the newest entries from the cursor provided. If not present, the result will contain all the audit log from the org')
program.parse(process.argv);
program.parse(process.argv)
const configLocation = program.cfg || './.ghec-audit-log';
let config = {};
const configLocation = program.cfg || './.ghec-audit-log'
let config = {}
try {
config = YAML.parse(fs.readFileSync(configLocation, 'utf8'));
} catch(e) {
console.log(`${configLocation} file missing. Path parameters will apply`)
config = YAML.parse(fs.readFileSync(configLocation, 'utf8'))
} catch (e) {
console.log(`${configLocation} file missing. Path parameters will apply`)
}
//TODO idea: maybe add support for other formats like PUTVAL to forward the data in an easier way
const {cursor, pretty, limit, token, org, outputFile} = validateInput(program, config)
// TODO idea: maybe add support for other formats like PUTVAL to forward the data in an easier way
const { cursor, pretty, limit, token, org, outputFile } = validateInput(program, config)
/**
* Function containing all the queries
*/
async function queryAuditLog() {
// Select the query to run
let queryRunner;
if (cursor) {
queryRunner = () => requestEntries(graphqlWithAuth, org, limit, cursor);
} else {
queryRunner = () => requestEntries(graphqlWithAuth, org, limit);
}
async function queryAuditLog () {
// Select the query to run
let queryRunner
if (cursor) {
queryRunner = () => requestEntries(graphqlWithAuth, org, limit, cursor)
} else {
queryRunner = () => requestEntries(graphqlWithAuth, org, limit)
}
// Run the query and store the most recent cursor
let {data, newestCursorId} = await queryRunner();
let entries = data;
if(newestCursorId) fs.writeFileSync('.last-cursor-update', newestCursorId);
// Run the query and store the most recent cursor
const { data, newestCursorId } = await queryRunner()
const entries = data
if (newestCursorId) fs.writeFileSync('.last-cursor-update', newestCursorId)
// Return the data
if (pretty === true) {
return JSON.stringify(entries, null, 4);
} else {
return JSON.stringify(entries);
}
// Return the data
if (pretty === true) {
return JSON.stringify(entries, null, 4)
} else {
return JSON.stringify(entries)
}
}
// Execute the request and print the result
const graphqlWithAuth = graphql.defaults({
headers: {
authorization: `token ${token}`,
},
});
headers: {
authorization: `token ${token}`
}
})
queryAuditLog()
.then((data) => {
if(outputFile){
fs.writeFileSync(outputFile, data);
} else {
console.log(data)
}
if (outputFile) {
fs.writeFileSync(outputFile, data)
} else {
console.log(data)
}
})
.catch((err) => {
console.error(err);
process.exit(1);
});
console.error(err)
process.exit(1)
})

Просмотреть файл

@ -1,50 +1,50 @@
const {allEntriesQuery} = require('./ghec-audit-log-queries');
const { allEntriesQuery } = require('./ghec-audit-log-queries')
async function requestEntries(requestExecutor, org, limit, cursor){
let entries = [];
let variables = {
"org": org,
"page": null,
};
async function requestEntries (requestExecutor, org, limit, cursor) {
let entries = []
const variables = {
org: org,
page: null
}
let hasNextPage = true;
let firstPageCursorId = null;
let foundCursor = false;
let hasLimit = limit || false;
let limitReached = false;
while(hasNextPage && !foundCursor && !limitReached) {
const data = await requestExecutor(allEntriesQuery, variables);
let newEntries = data.organization.auditLog.nodes;
let hasNextPage = true
let firstPageCursorId = null
let foundCursor = false
const hasLimit = limit || false
let limitReached = false
while (hasNextPage && !foundCursor && !limitReached) {
const data = await requestExecutor(allEntriesQuery, variables)
let newEntries = data.organization.auditLog.nodes
//Cursor check
if(cursor != null){
let index = newEntries.findIndex((elem) => elem.id === cursor);
if(index !== -1){
newEntries = newEntries.slice(0, index);
foundCursor = true;
// Cursor check
if (cursor != null) {
const index = newEntries.findIndex((elem) => elem.id === cursor)
if (index !== -1) {
newEntries = newEntries.slice(0, index)
foundCursor = true
}
}
entries = entries.concat(newEntries);
hasNextPage = data.organization.auditLog.pageInfo.hasNextPage;
variables.page = data.organization.auditLog.pageInfo.endCursor;
entries = entries.concat(newEntries)
hasNextPage = data.organization.auditLog.pageInfo.hasNextPage
variables.page = data.organization.auditLog.pageInfo.endCursor
//Check limit
if(hasLimit){
if(entries.length >= limit) {
entries = entries.slice(0, limit);
// Check limit
if (hasLimit) {
if (entries.length >= limit) {
entries = entries.slice(0, limit)
}
limitReached = true;
limitReached = true
}
//Store last cursor request
if(!firstPageCursorId && newEntries.length !== 0) {
// Store last cursor request
if (!firstPageCursorId && newEntries.length !== 0) {
firstPageCursorId = newEntries[0].id
}
}
return {data: entries, newestCursorId: firstPageCursorId};
return { data: entries, newestCursorId: firstPageCursorId }
}
module.exports = {
module.exports = {
requestEntries
};
}

Просмотреть файл

@ -1,11 +1,11 @@
//--- model objects
// --- model objects
const sponsorsListing = `{
createdAt
fullDescription
id
shortDescription
slug
}`;
}`
const userStatus = `{
createdAt
@ -15,7 +15,7 @@ const userStatus = `{
indicatesLimitedAvailability
message
updatedAt
}`;
}`
const user = `{
anyPinnableItems
@ -49,7 +49,7 @@ const user = `{
viewerCanFollow
viewerIsFollowing
websiteUrl
}`;
}`
const bot = `{
createdAt
@ -59,7 +59,7 @@ const bot = `{
resourcePath
updatedAt
url
}`;
}`
const orgIdentityProvider = `{
digestMethod
@ -68,7 +68,7 @@ const orgIdentityProvider = `{
issuer
signatureMethod
ssoUrl
}`;
}`
const organization = `{
anyPinnableItems
@ -102,7 +102,7 @@ const organization = `{
viewerCanCreateTeams
viewerIsAMember
websiteUrl
}`;
}`
const team = `{
avatarUrl
@ -131,7 +131,7 @@ const team = `{
viewerCanAdminister
viewerCanSubscribe
viewerSubscription
}`;
}`
const codeOfConduct = `{
body
@ -140,32 +140,32 @@ const codeOfConduct = `{
name
resourcePath
url
}`;
}`
const gitObjectId = `{
abbreviatedOid
commitResourcePath
commitUrl
id
}`;
}`
const ref = `{
id
name
prefix
target ${gitObjectId}
}`;
}`
const fundingLink = `{
platform
url
}`;
}`
const licenseRule = `{
description
key
label
}`;
}`
const license = `{
body
@ -183,18 +183,18 @@ const license = `{
pseudoLicense
spdxId
url
}`;
}`
const repositoryOwner = `{
__typename
login
}`;
}`
const language = `{
color
id
name
}`;
}`
const repository = `{
codeOfConduct ${codeOfConduct}
@ -245,14 +245,14 @@ const repository = `{
viewerHasStarred
viewerPermission
viewerSubscription
}`;
}`
const actor = `{
__typename
... on Bot ${bot}
... on Organization ${organization}
... on User ${user}
}`;
}`
const actorLocation = `{
city
@ -260,45 +260,45 @@ const actorLocation = `{
countryCode
region
regionCode
}`;
}`
const restoreOrganization = `{
organization ${organization}
organizationName
organizationResourcePath
organizationUrl
}`;
}`
const restoreRepository = `{
repository ${repository}
repositoryName
repositoryResourcePath
repositoryUrl
}`;
}`
const restoreTeam = `{
team ${team}
teamName
teamResourcePath
teamUrl
}`;
}`
const restoreMembership = `{
... on OrgRestoreMemberMembershipOrganizationAuditEntryData ${restoreOrganization}
... on OrgRestoreMemberMembershipRepositoryAuditEntryData ${restoreRepository}
... on OrgRestoreMemberMembershipTeamAuditEntryData ${restoreTeam}
}`;
}`
const topic = `{
id
name
viewerHasStarred
}`;
}`
//--- Start of generic audit entries
// --- Start of generic audit entries
const nodeEntryData = `... on Node {
id
}`;
}`
const auditEntry = `... on AuditEntry {
action
@ -314,41 +314,41 @@ const auditEntry = `... on AuditEntry {
userLogin
userResourcePath
userUrl
}`;
}`
const organizationAuditEntryData = `... on OrganizationAuditEntryData {
# organization {organization}
organizationName
organizationResourcePath
organizationUrl
}`;
}`
const repositoruAuditEntryData = `... on RepositoryAuditEntryData {
# repository {repository}
repositoryName
repositoryResourcePath
repositoryUrl
}`;
}`
const topicAuditEntryData = `... on TopicAuditEntryData {
# topic {topic}
topic ${topic}
topicName
}`;
}`
const enterpriseAuditEntryData = `... on EnterpriseAuditEntryData {
enterpriseResourcePath
enterpriseSlug
enterpriseUrl
}`;
}`
const teamAuditEntryData = `... on TeamAuditEntryData {
team ${team}
teamName
teamResourcePath
teamUrl
}`;
}`
//--- Start of specific audit entries
// --- Start of specific audit entries
const oauthApplicationCreateAuditEntry = `... on OauthApplicationCreateAuditEntry {
applicationUrl
callbackUrl
@ -356,75 +356,75 @@ const oauthApplicationCreateAuditEntry = `... on OauthApplicationCreateAuditEntr
oauthApplicationUrl
rateLimit
state
}`;
}`
const orgAddBillingManagerAuditEntry = `... on OrgAddBillingManagerAuditEntry {
invitationEmail
}`;
}`
const orgAddMemberAuditEntry = `... on OrgAddMemberAuditEntry {
permission
}`;
}`
const orgBlockUserAuditEntry = `... on OrgBlockUserAuditEntry {
blockedUserName
blockedUserResourcePath
blockedUserUrl
}`;
}`
const orgCreateAuditEntry = `... on OrgCreateAuditEntry {
billingPlan
}`;
}`
const orgDisableSamlAuditEntry = `... on OrgDisableSamlAuditEntry {
digestMethodUrl
issuerUrl
signatureMethodUrl
singleSignOnUrl
}`;
}`
const orgEnableSamlAuditEntry = `... on OrgEnableSamlAuditEntry {
digestMethodUrl
issuerUrl
signatureMethodUrl
singleSignOnUrl
}`;
}`
const orgInviteMemberAuditEntry = `... on OrgInviteMemberAuditEntry {
email
}`;
}`
const orgOauthAppAccessApprovedAuditEntry = `... on OrgOauthAppAccessApprovedAuditEntry {
oauthApplicationName
oauthApplicationResourcePath
oauthApplicationUrl
}`;
}`
const orgOauthAppAccessDeniedAuditEntry = `... on OrgOauthAppAccessDeniedAuditEntry {
oauthApplicationName
oauthApplicationResourcePath
oauthApplicationUrl
}`;
}`
const orgOauthAppAccessRequestedAuditEntry = `... on OrgOauthAppAccessRequestedAuditEntry {
oauthApplicationName
oauthApplicationResourcePath
oauthApplicationUrl
}`;
}`
const orgRemoveBillingManagerAuditEntry = `... on OrgRemoveBillingManagerAuditEntry {
reason
}`;
}`
const orgRemoveMemberAuditEntry = `... on OrgRemoveMemberAuditEntry {
membershipTypes
reason
}`;
}`
const orgRemoveOutsideCollaboratorAuditEntry = `... on OrgRemoveOutsideCollaboratorAuditEntry {
membershipTypes
reason
}`;
}`
const orgRestoreMemberAuditEntry = `... on OrgRestoreMemberAuditEntry {
restoredCustomEmailRoutingsCount
@ -434,71 +434,71 @@ const orgRestoreMemberAuditEntry = `... on OrgRestoreMemberAuditEntry {
restoredRepositoriesCount
restoredRepositoryStarsCount
restoredRepositoryWatchesCount
}`;
}`
const orgUnblockUserAuditEntry = `... on OrgUnblockUserAuditEntry {
blockedUserName
blockedUserResourcePath
blockedUserUrl
}`;
}`
const orgUpdateDefaultRepositoryPermissionAuditEntry = `... on OrgUpdateDefaultRepositoryPermissionAuditEntry {
permission
permissionWas
}`;
}`
const orgUpdateMemberAuditEntry = `... on OrgUpdateMemberAuditEntry {
permission
permissionWas
}`;
}`
const orgUpdateMemberRepositoryCreationPermissionAuditEntry = `... on OrgUpdateMemberRepositoryCreationPermissionAuditEntry {
canCreateRepositories
visibility
}`;
}`
const orgUpdateMemberRepositoryInvitationPermissionAuditEntry = `... on OrgUpdateMemberRepositoryInvitationPermissionAuditEntry {
canInviteOutsideCollaboratorsToRepositories
}`;
}`
const repoAccessAuditEntry = `... on RepoAccessAuditEntry {
visibility
}`;
}`
const repoAddMemberAuditEntry = `... on RepoAddMemberAuditEntry {
visibility
}`;
}`
const repoArchivedAuditEntry = `... on RepoArchivedAuditEntry {
visibility
}`;
}`
const repoChangeMergeSettingAuditEntry = `... on RepoChangeMergeSettingAuditEntry {
isEnabled
mergeType
}`;
}`
const repoCreateAuditEntry = `... on RepoCreateAuditEntry {
forkParentName
forkSourceName
visibility
}`;
}`
const repoDestroyAuditEntry = `... on RepoDestroyAuditEntry {
visibility
}`;
}`
const repoRemoveMemberAuditEntry = `... on RepoRemoveMemberAuditEntry {
visibility
}`;
}`
const teamAddMemberAuditEntry = `... on TeamAddMemberAuditEntry {
isLdapMapped
}`;
}`
const teamAddRepositoryAuditEntry = `... on TeamAddRepositoryAuditEntry {
isLdapMapped
}`;
}`
const teamChangeParentTeamAuditEntry = `... on TeamChangeParentTeamAuditEntry {
isLdapMapped
@ -510,16 +510,15 @@ const teamChangeParentTeamAuditEntry = `... on TeamChangeParentTeamAuditEntry {
parentTeamWas ${team}
parentTeamWasResourcePath
parentTeamWasUrl
}`;
}`
const teamRemoveMemberAuditEntry = `... on TeamRemoveMemberAuditEntry {
isLdapMapped
}`;
}`
const teamRemoveRepositoryAuditEntry = `... on TeamRemoveRepositoryAuditEntry {
isLdapMapped
}`;
}`
const ghecAuditLogEntries = `
__typename
@ -564,7 +563,7 @@ const ghecAuditLogEntries = `
${teamChangeParentTeamAuditEntry}
${teamRemoveMemberAuditEntry}
${teamRemoveRepositoryAuditEntry}
`;
`
// All this types have no additional properties and are covered by EntryData types
// Empty covered by supertypes
@ -648,4 +647,4 @@ const ghecAuditLogEntries = `
// ${repositoryVisibilityChangeDisableAuditEntry}
// ${repositoryVisibilityChangeEnableAuditEntry}
module.exports = ghecAuditLogEntries;
module.exports = ghecAuditLogEntries

Просмотреть файл

@ -1,4 +1,4 @@
const auditLogEntries = require('./ghec-audit-log-entries');
const auditLogEntries = require('./ghec-audit-log-entries')
const allEntriesQuery = `
query($org: String!, $page: String) {
@ -13,8 +13,8 @@ query($org: String!, $page: String) {
}
}
}
}`;
}`
module.exports = {
allEntriesQuery,
};
allEntriesQuery
}

Просмотреть файл

@ -14,7 +14,7 @@ function validateInput (program, config) {
// Validate correctness
const alphanumericRegex = /^[a-z0-9]+$/i
const base64Regex = '(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)';
const base64Regex = '(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)'
const orgRegex = /^[a-z\d]+(?:-?[a-z\d]+)*$/i
const constraints = {
cursor: {
@ -24,13 +24,13 @@ function validateInput (program, config) {
},
pretty: {
type: 'boolean',
presence: true,
presence: true
},
limit: {
presence: false,
numericality: {
onlyInteger: true,
greaterThan: 0,
greaterThan: 0
}
},
token: {
@ -52,28 +52,28 @@ function validateInput (program, config) {
},
outputFile: {
type: 'string',
presence: false,
presence: false
}
}
//Verify validation
// Verify validation
const validation = validate(parsed, constraints)
if(!validate.isEmpty(validation)) {
if (!validate.isEmpty(validation)) {
throw new Error(JSON.stringify(validation))
}
//Check that we can write into that file
if(parsed.outputFile){
// Check that we can write into that file
if (parsed.outputFile) {
try {
fs.openSync(parsed.outputFile, 'w')
}catch (e) {
} catch (e) {
throw new Error(`The output file ${parsed.outputFile} cannot be written or the path does not exist. ${e.message}`)
}
}
//Check that if we are in GitHub actions the file is expected to be within the workspace
if(process.env.GITHUB_ACTIONS) {
// Check that if we are in GitHub actions the file is expected to be within the workspace
if (process.env.GITHUB_ACTIONS) {
const filePath = path.join(process.env.GITHUB_WORKSPACE, parsed.outputFile)
const {dir} = path.parse(filePath)
const { dir } = path.parse(filePath)
if (dir.indexOf(process.env.GITHUB_WORKSPACE) < 0) {
throw new Error(`${parsed.outputFile} is not allowed. The directory should be within ${process.env.GITHUB_WORKSPACE}`)
@ -85,4 +85,4 @@ function validateInput (program, config) {
module.exports = {
validateInput
}
}

1865
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -4,7 +4,8 @@
"description": "",
"main": "ghec-audit-log-cli.js",
"scripts": {
"start": "node ghec-audit-log-cli"
"start": "node ghec-audit-log-cli",
"lint": "standard"
},
"repository": {
"type": "git",
@ -32,5 +33,8 @@
},
"bin": {
"ghec-audit-log-cli": "./ghec-audit-log-cli.js"
},
"devDependencies": {
"standard": "^16.0.3"
}
}