Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ So until those issues are resolved, `dotnet list package` needs some workarounds

This tool tries to do just that. It wraps `dotnet list package` and interprets the output for vulnerabilities. Anything found will return in a non-zero return code, and you get some nice markdown to make your PRs obvious. And because it's a `dotnet tool`, using it in a CI pipeline is as easy as using it on your dev machine.

## If you want to use this as a Github Action

A Github Action is available - see [pkgchk-action](https://github.com/tonycknight/pkgchk-action).

## What you need to install it

:warning: This tool only works with .Net SDK 7.0.200 or higher.
Expand Down
65 changes: 65 additions & 0 deletions src/pkgchk-cli/Commands.fs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@ type PackageCheckCommandSettings() =
[<DefaultValue(false)>]
member val NoBanner = false with get, set

[<CommandOption("--github-token", IsHidden = true)>]
[<Description("A Github token.")>]
[<DefaultValue("")>]
member val GithubToken = "" with get, set

[<CommandOption("--github-repo", IsHidden = true)>]
[<Description("The name of the Github repository in the form <owner>/<repo>, e.g. github/octokit.")>]
[<DefaultValue("")>]
member val GithubRepo = "" with get, set

[<CommandOption("--github-title", IsHidden = true)>]
[<Description("The Github report title.")>]
[<DefaultValue("")>]
member val GithubSummaryTitle = "" with get, set

[<CommandOption("--github-pr", IsHidden = true)>]
[<Description("Pull request ID.")>]
[<DefaultValue("")>]
member val GithubPrId = "" with get, set

[<ExcludeFromCodeCoverage>]
type PackageCheckCommand(nuget: Tk.Nuget.INugetClient) =
inherit Command<PackageCheckCommandSettings>()
Expand Down Expand Up @@ -139,9 +159,29 @@ type PackageCheckCommand(nuget: Tk.Nuget.INugetClient) =
override _.Execute(context, settings) =
let trace = trace settings.TraceLogging

settings.SeverityLevels <- settings.SeverityLevels |> Array.filter String.isNotEmpty

if settings.NoBanner |> not then
nuget |> App.banner |> console

if String.isNotEmpty settings.GithubPrId then
if String.isEmpty settings.GithubToken then
failwith "Missing Github token."

if String.isEmpty settings.GithubRepo then
failwith "Missing Github repository. Use the form <owner>/<name>."

let repo = GithubRepo.repo settings.GithubRepo

if repo |> fst |> String.isEmpty then
failwith "The repository owner is missing. Use the form <owner>/<name>."

if repo |> snd |> String.isEmpty then
failwith "The repository name is missing. Use the form <owner>/<name>."

if String.isInt settings.GithubPrId |> not then
failwith "The PR ID must be an integer."

match runRestore settings trace with
| Choice2Of2 error -> error |> returnError
| _ ->
Expand Down Expand Up @@ -196,4 +236,29 @@ type PackageCheckCommand(nuget: Tk.Nuget.INugetClient) =
|> Console.italic
|> console

if
String.isNotEmpty settings.GithubToken
&& String.isNotEmpty settings.GithubRepo
&& String.isNotEmpty settings.GithubPrId
then
let prId = String.toInt settings.GithubPrId
let repo = GithubRepo.repo settings.GithubRepo

let markdown =
(hits, errorHits, hitCounts, settings.SeverityLevels)
|> Markdown.generate
|> String.joinLines

let comment = GithubComment.create settings.GithubSummaryTitle markdown

trace $"Posting {comment.title} report to Github repo {repo}..."

let client = Github.client settings.GithubToken

let _ = (comment |> Github.setPrComment client repo prId).Result

$"{comment.title} report sent to Github." |> Console.italic |> console



errorHits |> returnCode
86 changes: 86 additions & 0 deletions src/pkgchk-cli/Github.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
namespace pkgchk

open System
open System.Diagnostics.CodeAnalysis
open Octokit

type GithubComment =
{ title: string
body: string }

static member create title body =
{ GithubComment.title = (String.defaultValue "pkgchk summary" title)
body = body }

module GithubRepo =

let repo (value: string) =
match value.Split('/', StringSplitOptions.None) with
| [| owner; repo |] -> (owner, repo)
| _ -> ("", value)

module Github =

[<ExcludeFromCodeCoverage>]
let client token =
let header = new ProductHeaderValue(App.packageId)
let client = new GitHubClient(header)
client.Credentials <- new Credentials(token)
client :> IGitHubClient

let getIssue (client: IGitHubClient) (owner: string, repo) id =
task {
try
let! issue = client.Issue.Get(owner, repo, id)
return Some issue
with ex ->
return None
}

let getIssueComments (client: IGitHubClient) (owner: string, repo) id =
task {
try
let! issue = getIssue client (owner, repo) id

let! comments =
match issue with
| Some issue ->
task {
let! x = client.Issue.Comment.GetAllForIssue(owner, repo, id)
return x |> List.ofSeq
}
| None -> task { return [] }

return comments
with ex ->
return []
}

let setPrComment (client: IGitHubClient) (owner, repo) prId (comment: GithubComment) =
task {
let commentTitle = $"# {comment.title}"
let commentBody = $"{commentTitle}{Environment.NewLine}{comment.body}"

// As there's no concret mechanism in Octokit to affinitise comments, we must use titles as the discriminator.
let! comments = getIssueComments client (owner, repo) prId

let previousComment =
comments
|> Seq.filter (fun c -> c.Body.StartsWith(commentTitle, StringComparison.InvariantCulture))
|> Seq.tryHead

let! newComment =
match previousComment with
| Some c ->
task {
let! x = client.Issue.Comment.Update(owner, repo, c.Id, commentBody)
return x
}
| None ->
task {
let! x = client.Issue.Comment.Create(owner, repo, prId, commentBody)
return x
}

return newComment
}
4 changes: 2 additions & 2 deletions src/pkgchk-cli/Markdown.fs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module Markdown =
""
"---"
""
"_With :heart: by [pkgchk-cli](https://github.com/tonycknight/pkgchk-cli). Thank you for using my software._"
"_With :heart: from [pkgchk-cli](https://github.com/tonycknight/pkgchk-cli) Thank you for using my software_"
""
"---"
}
Expand Down Expand Up @@ -119,7 +119,7 @@ module Markdown =
let formatHitGroup (hit: (string * seq<ScaHit>)) =
let grpHdr =
seq {
"| | | | |"
"| Kind | Severity | Package | Info |"
"|-|-|-|-|"
}

Expand Down
14 changes: 14 additions & 0 deletions src/pkgchk-cli/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ module String =
let nullToEmpty (value: string) =
if obj.ReferenceEquals(value, null) then "" else value

[<DebuggerStepThrough>]
let defaultValue (defaultValue: string) (value: string) =
if isNotEmpty value then value else defaultValue

[<DebuggerStepThrough>]
let leading (len: int) (value: string) =
let len2 = System.Math.Min(value.Length, len)
Expand All @@ -64,6 +68,16 @@ module String =
let escapeMarkup (value: string) =
value.Replace("[", "[[").Replace("]", "]]")

[<DebuggerStepThrough>]
let isInt (value: string) = Int32.TryParse value |> fst

[<DebuggerStepThrough>]
let toInt (value: string) =
match Int32.TryParse value with
| (true, x) -> x
| _ -> 0


module ReturnCodes =

[<Literal>]
Expand Down
2 changes: 2 additions & 0 deletions src/pkgchk-cli/pkgchk-cli.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@
<Compile Include="Console.fs" />
<Compile Include="Markdown.fs" />
<Compile Include="App.fs" />
<Compile Include="Github.fs" />
<Compile Include="Commands.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FSharp.Data" Version="6.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Octokit" Version="9.1.1" />
<PackageReference Include="Spectre.Console.Cli" Version="0.48.0" />
<PackageReference Include="Tk.Nuget" Version="0.1.36" />
</ItemGroup>
Expand Down
11 changes: 11 additions & 0 deletions tests/pkgchk-cli.tests/GithubCommentTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace pkgchk.tests

open FsCheck.Xunit

module GithubCommentTests =

[<Property(Arbitrary = [| typeof<AlphaNumericString> |], Verbose = true)>]
let ``create produces comment`` (title: string, body: string) =
let r = pkgchk.GithubComment.create title body

r.title = title && r.body = body
18 changes: 18 additions & 0 deletions tests/pkgchk-cli.tests/GithubRepoTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace pkgchk.tests

open System
open FsCheck.Xunit

module GithubRepoTests =

[<Property(Arbitrary = [| typeof<AlphaNumericString> |], Verbose = true)>]
let ``repo constructs owner/repo`` (name: string[]) =
let input = name |> pkgchk.String.join "/"

let expected =
if name.Length = 2 then
(name.[0], name.[1])
else
("", input)

input |> pkgchk.GithubRepo.repo = expected
Loading