Skip to content
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

chore: auto label discussions #5259

Merged
merged 3 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/actions/trivy-triage/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.PHONEY: test
test: helpers.js helpers.test.js
node --test helpers.test.js
29 changes: 29 additions & 0 deletions .github/actions/trivy-triage/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: 'trivy-discussion-triage'
description: 'automatic triage of Trivy discussions'
inputs:
discussion_num:
description: 'Discussion number to triage'
required: false
runs:
using: "composite"
steps:
- name: Conditionally label discussions based on category and content
env:
GH_TOKEN: ${{ github.token }}
uses: actions/github-script@v6
with:
script: |
const {detectDiscussionLabels, fetchDiscussion, labelDiscussion } = require('${{ github.action_path }}/helpers.js');
const config = require('${{ github.action_path }}/config.json');
discussionNum = parseInt(${{ inputs.discussion_num }});
let discussion;
if (discussionNum > 0) {
discussion = (await fetchDiscussion(github, context.repo.owner, context.repo.repo, discussionNum)).repository.discussion;
} else {
discussion = context.payload.discussion;
}
const labels = detectDiscussionLabels(discussion, config.discussionLabels);
if (labels.length > 0) {
console.log(`Adding labels ${labels} to discussion ${discussion.node_id}`);
labelDiscussion(github, discussion.node_id, labels);
}
14 changes: 14 additions & 0 deletions .github/actions/trivy-triage/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"discussionLabels": {
"Container Image":"LA_kwDOCsUTCM75TTQU",
"Filesystem":"LA_kwDOCsUTCM75TTQX",
"Git Repository":"LA_kwDOCsUTCM75TTQk",
"Virtual Machine Image":"LA_kwDOCsUTCM8AAAABMpz1bw",
"Kubernetes":"LA_kwDOCsUTCM75TTQv",
"AWS":"LA_kwDOCsUTCM8AAAABMpz1aA",
"Vulnerability":"LA_kwDOCsUTCM75TTPa",
"Misconfiguration":"LA_kwDOCsUTCM75TTP8",
"License":"LA_kwDOCsUTCM77ztRR",
"Secret":"LA_kwDOCsUTCM75TTQL"
}
}
69 changes: 69 additions & 0 deletions .github/actions/trivy-triage/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module.exports = {
detectDiscussionLabels: (discussion, configDiscussionLabels) => {
res = [];
const discussionId = discussion.id;
const category = discussion.category.name;
const body = discussion.body;
if (category !== "Ideas") {
consolt.log("skipping discussion with category ${category} and body ${body}");
}
const scannerPattern = /### Scanner\n\n(.+)/;
const scannerFound = body.match(scannerPattern);
if (scannerFound && scannerFound.length > 1) {
res.push(configDiscussionLabels[scannerFound[1]]);
}
const targetPattern = /### Target\n\n(.+)/;
const targetFound = body.match(targetPattern);
if (targetFound && targetFound.length > 1) {
res.push(configDiscussionLabels[targetFound[1]]);
}
return res;
},
fetchDiscussion: async (github, owner, repo, discussionNum) => {
const query = `query Discussion ($owner: String!, $repo: String!, $discussion_num: Int!){
repository(name: $repo, owner: $owner) {
discussion(number: $discussion_num) {
number,
id,
body,
category {
id,
name
},
labels(first: 100) {
edges {
node {
id,
name
}
}
}
}
}
}`;
const vars = {
owner: owner,
repo: repo,
discussion_num: discussionNum
};
return github.graphql(query, vars);
},
labelDiscussion: async (github, discussionId, labelIds) => {
const query = `mutation AddLabels($labelId: ID!, $labelableId:ID!) {
addLabelsToLabelable(
input: {labelIds: [$labelId], labelableId: $labelableId}
) {
clientMutationId
}
}`;
// TODO: add all labels in one call
labelIds.forEach((labelId) => {
const vars = {
labelId: labelId,
labelableId: discussionId
};
github.graphql(query, vars);
});
}
};

77 changes: 77 additions & 0 deletions .github/actions/trivy-triage/helpers.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const assert = require('node:assert/strict');
const { describe, it } = require('node:test');
const {detectDiscussionLabels} = require('./helpers.js');

const configDiscussionLabels = {
"Container Image":"ContainerImageLabel",
"Filesystem":"FilesystemLabel",
"Vulnerability":"VulnerabilityLabel",
"Misconfiguration":"MisconfigurationLabel",
};

describe('trivy-triage', async function() {
describe('detectDiscussionLabels', async function() {
it('detect scanner label', async function() {
const discussion = {
body: 'hello hello\nbla bla.\n### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.',
category: {
name: 'Ideas'
}
};
const labels = detectDiscussionLabels(discussion, configDiscussionLabels);
assert(labels.includes('VulnerabilityLabel'));
});
it('detect target label', async function() {
const discussion = {
body: 'hello hello\nbla bla.\n### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.',
category: {
name: 'Ideas'
}
};
const labels = detectDiscussionLabels(discussion, configDiscussionLabels);
assert(labels.includes('ContainerImageLabel'));
});
it('detect label when it is first', async function() {
const discussion = {
body: '### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.',
category: {
name: 'Ideas'
}
};
const labels = detectDiscussionLabels(discussion, configDiscussionLabels);
assert(labels.includes('ContainerImageLabel'));
});
it('detect label when it is last', async function() {
const discussion = {
body: '### Scanner\n\nVulnerability\n### Target\n\nContainer Image',
category: {
name: 'Ideas'
}
};
const labels = detectDiscussionLabels(discussion, configDiscussionLabels);
assert(labels.includes('ContainerImageLabel'));
});
it('detect scanner and target labels', async function() {
const discussion = {
body: 'hello hello\nbla bla.\n### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.',
category: {
name: 'Ideas'
}
};
const labels = detectDiscussionLabels(discussion, configDiscussionLabels);
assert(labels.includes('ContainerImageLabel'));
assert(labels.includes('VulnerabilityLabel'));
});
it('not detect other labels', async function() {
const discussion = {
body: 'hello hello\nbla bla.\n### Scanner\n\nVulnerability\n### Target\n\nContainer Image\nbye bye.',
category: {
name: 'Ideas'
}
};
const labels = detectDiscussionLabels(discussion, configDiscussionLabels);
assert(!labels.includes('FilesystemLabel'));
assert(!labels.includes('MisconfigurationLabel'));
});
});
});
65 changes: 65 additions & 0 deletions .github/actions/trivy-triage/test/discussion-payload-sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"active_lock_reason": null,
"answer_chosen_at": null,
"answer_chosen_by": null,
"answer_html_url": null,
"author_association": "OWNER",
"body": "### Description\n\nlfdjs lfkdj dflsakjfd ';djk \r\nfadfd \r\nasdlkf \r\na;df \r\ndfsal;kfd ;akjl\n\n### Target\n\nContainer Image\n\n### Scanner\n\nMisconfiguration",
"category": {
"created_at": "2023-07-02T10:14:46.000+03:00",
"description": "Share ideas for new features",
"emoji": ":bulb:",
"id": 39743708,
"is_answerable": false,
"name": "Ideas",
"node_id": "DIC_kwDOE0GiPM4CXnDc",
"repository_id": 323068476,
"slug": "ideas",
"updated_at": "2023-07-02T10:14:46.000+03:00"
},
"comments": 0,
"created_at": "2023-09-11T08:40:11Z",
"html_url": "https://github.com/itaysk/testactions/discussions/9",
"id": 5614504,
"locked": false,
"node_id": "D_kwDOE0GiPM4AVauo",
"number": 9,
"reactions": {
"+1": 0,
"-1": 0,
"confused": 0,
"eyes": 0,
"heart": 0,
"hooray": 0,
"laugh": 0,
"rocket": 0,
"total_count": 0,
"url": "https://api.github.com/repos/itaysk/testactions/discussions/9/reactions"
},
"repository_url": "https://api.github.com/repos/itaysk/testactions",
"state": "open",
"state_reason": null,
"timeline_url": "https://api.github.com/repos/itaysk/testactions/discussions/9/timeline",
"title": "Title title",
"updated_at": "2023-09-11T08:40:11Z",
"user": {
"avatar_url": "https://avatars.githubusercontent.com/u/1161307?v=4",
"events_url": "https://api.github.com/users/itaysk/events{/privacy}",
"followers_url": "https://api.github.com/users/itaysk/followers",
"following_url": "https://api.github.com/users/itaysk/following{/other_user}",
"gists_url": "https://api.github.com/users/itaysk/gists{/gist_id}",
"gravatar_id": "",
"html_url": "https://github.com/itaysk",
"id": 1161307,
"login": "itaysk",
"node_id": "MDQ6VXNlcjExNjEzMDc=",
"organizations_url": "https://api.github.com/users/itaysk/orgs",
"received_events_url": "https://api.github.com/users/itaysk/received_events",
"repos_url": "https://api.github.com/users/itaysk/repos",
"site_admin": false,
"starred_url": "https://api.github.com/users/itaysk/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/itaysk/subscriptions",
"type": "User",
"url": "https://api.github.com/users/itaysk"
}
}
29 changes: 29 additions & 0 deletions .github/actions/trivy-triage/test/fetchDiscussion.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#! /bin/bash
# fetch discussion by discussion number
# requires authenticated gh cli, assumes repo but current git repository
# args:
# $1: discussion number, e.g 123, required

discussion_num="$1"
gh api graphql -F discussion_num="$discussion_num" -F repo="{repo}" -F owner="{owner}" -f query='
query Discussion ($owner: String!, $repo: String!, $discussion_num: Int!){
repository(name: $repo, owner: $owner) {
discussion(number: $discussion_num) {
number,
id,
body,
category {
id,
name
},
labels(first: 100) {
edges {
node {
id,
name
}
}
}
}
}
}'
16 changes: 16 additions & 0 deletions .github/actions/trivy-triage/test/fetchLabels.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#! /bin/bash
# fetch labels and their IDs
# requires authenticated gh cli, assumes repo but current git repository

gh api graphql -F repo="{repo}" -F owner="{owner}" -f query='
query GetLabelIds($owner: String!, $repo: String!, ) {
repository(name: $repo, owner: $owner) {
id
labels(first: 100) {
nodes {
id
name
}
}
}
}'
7 changes: 7 additions & 0 deletions .github/actions/trivy-triage/test/labelDiscussion.sh
itaysk marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#! /bin/bash
# add a label to a discussion
# requires authenticated gh cli, assumes repo but current git repository
# args:
# $1: discussion ID (not number!), e.g DIC_kwDOE0GiPM4CXnDc, required
# $2: label ID, e.g. MDU6TGFiZWwzNjIzNjY0MjQ=, required
# TODO
16 changes: 16 additions & 0 deletions .github/trivy-triage.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: trivy triage
itaysk marked this conversation as resolved.
Show resolved Hide resolved
on:
discussion:
types: [created]
workflow_dispatch:
inputs:
discussion_num:
required: true
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
itaysk marked this conversation as resolved.
Show resolved Hide resolved
- uses: ./.github/actions/trivy-triage
with:
discussion_num: ${{ github.event.inputs.discussion_num }}