Skip to content

Commit 2e7648c

Browse files
committed
retirements
1 parent 9c5b29e commit 2e7648c

11 files changed

+202
-47
lines changed

.tool-versions

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
gleam 1.1.0
2-
nodejs 21.6.1
2+
erlang 26.2.5

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2021 Benjamin Wireman
3+
Copyright (c) 2024 Benjamin Wireman
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

gleam.toml

+4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ tom = ">= 1.0.0 and < 2.0.0"
2424
stoiridh_version = ">= 0.1.0 and < 1.0.0"
2525
yamerl = ">= 0.10.0 and < 1.0.0"
2626
gleam_erlang = ">= 0.25.0 and < 1.0.0"
27+
gleam_hexpm = ">= 1.0.0 and < 2.0.0"
28+
gleam_hackney = ">= 1.2.0 and < 2.0.0"
29+
gleam_http = ">= 3.6.0 and < 4.0.0"
30+
gleam_json = ">= 1.0.1 and < 2.0.0"
2731

2832
[dev-dependencies]
2933
gleeunit = ">= 1.0.0 and < 2.0.0"

manifest.toml

+20-1
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,39 @@
22
# You typically do not need to edit this file
33

44
packages = [
5+
{ name = "birl", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "976CFF85D34D50F7775896615A71745FBE0C325E50399787088F941B539A0497" },
6+
{ name = "certifi", version = "2.12.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C" },
57
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
68
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
9+
{ name = "gleam_hackney", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "hackney"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "066B1A55D37DBD61CC72A1C4EDE43C6015B1797FAF3818C16FE476534C7B6505" },
10+
{ name = "gleam_hexpm", version = "1.0.0", build_tools = ["gleam"], requirements = ["birl", "gleam_stdlib"], otp_app = "gleam_hexpm", source = "hex", outer_checksum = "A5DF5D32BFDE84003B2C2183700D98E106A969C6C6EDE0363A50BB421AFF50B7" },
11+
{ name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
12+
{ name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" },
713
{ name = "gleam_stdlib", version = "0.37.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "5398BD6C2ABA17338F676F42F404B9B7BABE1C8DC7380031ACB05BBE1BCF3742" },
814
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
15+
{ name = "hackney", version = "1.20.1", build_tools = ["rebar3"], requirements = ["certifi", "idna", "metrics", "mimerl", "parse_trans", "ssl_verify_fun", "unicode_util_compat"], otp_app = "hackney", source = "hex", outer_checksum = "FE9094E5F1A2A2C0A7D10918FEE36BFEC0EC2A979994CFF8CFE8058CD9AF38E3" },
16+
{ name = "idna", version = "6.1.1", build_tools = ["rebar3"], requirements = ["unicode_util_compat"], otp_app = "idna", source = "hex", outer_checksum = "92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA" },
17+
{ name = "metrics", version = "1.0.1", build_tools = ["rebar3"], requirements = [], otp_app = "metrics", source = "hex", outer_checksum = "69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16" },
18+
{ name = "mimerl", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "mimerl", source = "hex", outer_checksum = "A1E15A50D1887217DE95F0B9B0793E32853F7C258A5CD227650889B38839FE9D" },
19+
{ name = "parse_trans", version = "3.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "parse_trans", source = "hex", outer_checksum = "620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A" },
20+
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
921
{ name = "shellout", version = "1.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "shellout", source = "hex", outer_checksum = "E2FCD18957F0E9F67E1F497FC9FF57393392F8A9BAEAEA4779541DE7A68DD7E0" },
1022
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
23+
{ name = "ssl_verify_fun", version = "1.1.7", build_tools = ["mix", "rebar3", "make"], requirements = [], otp_app = "ssl_verify_fun", source = "hex", outer_checksum = "FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8" },
1124
{ name = "stoiridh_version", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "stoiridh_version", source = "hex", outer_checksum = "298ABEA44DF37764A34C2E9190A84BF2770BC59DD9397C6DC7708040E5A0142B" },
25+
{ name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" },
1226
{ name = "tom", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "A5364613E3DBF77F38EFF81DA9F99324086D029EC2B2D44348762FBE38602311" },
27+
{ name = "unicode_util_compat", version = "0.7.0", build_tools = ["rebar3"], requirements = [], otp_app = "unicode_util_compat", source = "hex", outer_checksum = "25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521" },
1328
{ name = "yamerl", version = "0.10.0", build_tools = ["rebar3"], requirements = [], otp_app = "yamerl", source = "hex", outer_checksum = "346ADB2963F1051DC837A2364E4ACF6EB7D80097C0F53CBDC3046EC8EC4B4E6E" },
1429
]
1530

1631
[requirements]
1732
filepath = { version = ">= 1.0.0 and < 2.0.0" }
18-
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0"}
33+
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
34+
gleam_hackney = { version = ">= 1.2.0 and < 2.0.0" }
35+
gleam_hexpm = { version = ">= 1.0.0 and < 2.0.0" }
36+
gleam_http = { version = ">= 3.6.0 and < 4.0.0" }
37+
gleam_json = { version = ">= 1.0.1 and < 2.0.0"}
1938
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
2039
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
2140
shellout = { version = ">= 1.6.0 and < 2.0.0" }

src/go_over.gleam

+33-7
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,53 @@
11
import gleam/io
22
import gleam/list
3+
import gleam/option
34
import gleam/string
45
import go_over/advisories
6+
import go_over/packages
7+
import go_over/retired
8+
import go_over/warning
59
import shellout
610

711
pub fn main() {
812
let args = shellout.arguments()
913
let pull = list.any(args, fn(arg) { arg == "--skip" })
14+
let pkgs = packages.read_manifest("./manifest.toml")
1015

11-
case advisories.check_for_advisories("./manifest.toml", !pull) {
16+
let vulnerable_packages =
17+
advisories.check_for_advisories(pkgs, !pull)
18+
|> list.map(fn(p) {
19+
case p {
20+
#(pkg, adv) -> warning.adv_to_warning(pkg, adv)
21+
}
22+
})
23+
24+
let retired_packages =
25+
pkgs
26+
|> list.map(fn(pkg) {
27+
case retired.check_retired(pkg) {
28+
option.Some(ret) -> option.Some(#(pkg, ret))
29+
option.None -> option.None
30+
}
31+
})
32+
|> option.values
33+
|> list.map(fn(p) {
34+
case p {
35+
#(pkg, ret) -> warning.retired_to_warning(pkg, ret)
36+
}
37+
})
38+
39+
case list.append(retired_packages, vulnerable_packages) {
1240
[] ->
1341
shellout.style(
1442
"All good!",
1543
with: shellout.color(["brightgreen"]),
1644
custom: [],
1745
)
1846
vulns -> {
19-
let warnings =
20-
vulns
21-
|> list.map(advisories.print_adv)
22-
|> string.join("\n")
23-
24-
shellout.style(warnings, with: shellout.color(["red"]), custom: [])
47+
vulns
48+
|> list.map(warning.print)
49+
|> string.join("\n")
50+
|> shellout.style(with: shellout.color(["red"]), custom: [])
2551
}
2652
}
2753
|> io.print

src/go_over/advisories.gleam

+8-33
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import gleam/list
33
import gleam/option
44
import gleam/string
55
import go_over/comparisons
6+
import go_over/packages
67
import go_over/yaml
78
import shellout
89
import simplifile
9-
import stoiridh/version.{type Version}
10-
import tom
1110

1211
pub type ADV {
1312
ADV(name: String, vulnerable_version_ranges: List(String), file: String)
@@ -18,30 +17,6 @@ fn path() -> String {
1817
filepath.join(curr, ".go-over")
1918
}
2019

21-
type Package {
22-
Package(name: String, version: Version)
23-
}
24-
25-
fn read_manifest(path: String) {
26-
let assert Ok(res) = simplifile.read(path)
27-
let assert Ok(manifest) = tom.parse(res)
28-
let assert Ok(packages) = tom.get_array(manifest, ["packages"])
29-
list.map(packages, fn(p) {
30-
case p {
31-
tom.InlineTable(x) -> {
32-
let assert Ok(name) = tom.get_string(x, ["name"])
33-
let assert Ok(ver) = tom.get_string(x, ["version"])
34-
let assert Ok(semver) = version.parse(ver)
35-
36-
option.Some(Package(name, semver))
37-
}
38-
39-
_ -> option.None
40-
}
41-
})
42-
|> option.values
43-
}
44-
4520
fn read_adv(path: String) {
4621
let #(name, vulnerable_version_ranges) = yaml.parse(path)
4722
ADV(name, vulnerable_version_ranges, path)
@@ -61,7 +36,7 @@ fn read_all_adv() {
6136
})
6237
}
6338

64-
fn is_vulnerable(p: Package, advs: List(ADV)) -> List(ADV) {
39+
fn is_vulnerable(p: packages.Package, advs: List(ADV)) -> List(ADV) {
6540
list.map(advs, fn(adv) {
6641
case adv.name == p.name {
6742
False -> option.None
@@ -102,7 +77,7 @@ fn clone() {
10277
Nil
10378
}
10479

105-
pub fn check_for_advisories(manifest_path: String, pull: Bool) {
80+
pub fn check_for_advisories(packages: List(packages.Package), pull: Bool) {
10681
case pull {
10782
True -> {
10883
let assert _ = simplifile.delete(path())
@@ -112,15 +87,15 @@ pub fn check_for_advisories(manifest_path: String, pull: Bool) {
11287
False -> Nil
11388
}
11489

115-
let packages = read_manifest(manifest_path)
11690
let advs = read_all_adv()
11791

118-
list.flat_map(packages, fn(p) {
119-
case is_vulnerable(p, advs) {
120-
[] -> []
121-
vulns -> vulns
92+
list.map(packages, fn(pkg) {
93+
case is_vulnerable(pkg, advs) {
94+
[] -> option.None
95+
vulns -> option.Some(#(pkg, vulns))
12296
}
12397
})
98+
|> option.values
12499
}
125100

126101
pub fn print_adv(adv: ADV) {

src/go_over/packages.gleam

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import simplifile
2+
import tom
3+
import gleam/list
4+
import stoiridh/version.{type Version}
5+
import gleam/option
6+
7+
pub type Package {
8+
Package(name: String, version: Version, version_raw: String, direct: Bool)
9+
}
10+
11+
pub fn read_manifest(path: String) -> List(Package) {
12+
let assert Ok(res) = simplifile.read(path)
13+
let assert Ok(manifest) = tom.parse(res)
14+
let assert Ok(packages) = tom.get_array(manifest, ["packages"])
15+
list.map(packages, fn(p) {
16+
case p {
17+
tom.InlineTable(x) -> {
18+
let assert Ok(name) = tom.get_string(x, ["name"])
19+
let assert Ok(ver) = tom.get_string(x, ["version"])
20+
let assert Ok(semver) = version.parse(ver)
21+
22+
option.Some(Package(name, semver, ver, True))
23+
}
24+
25+
_ -> option.None
26+
}
27+
})
28+
|> option.values
29+
}

src/go_over/retired.gleam

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import gleam/hackney
2+
import gleam/hexpm
3+
import gleam/http/request
4+
import gleam/json
5+
import gleam/option
6+
import go_over/packages
7+
8+
pub fn check_retired(pkg: packages.Package) {
9+
// Prepare a HTTP request record
10+
let assert Ok(request) =
11+
request.to(
12+
"https://hex.pm/api/packages/"
13+
<> pkg.name
14+
<> "/releases/"
15+
<> pkg.version_raw,
16+
)
17+
18+
// Send the HTTP request to the server
19+
let assert Ok(resp) =
20+
request
21+
|> request.prepend_header("accept", "application/json")
22+
|> hackney.send
23+
24+
let assert Ok(release) = json.decode(resp.body, hexpm.decode_release)
25+
26+
release.retirement
27+
}
28+
29+
pub fn print_ret(ret: hexpm.ReleaseRetirement) -> String {
30+
let reason = hexpm.retirement_reason_to_string(ret.reason)
31+
case ret.message {
32+
option.Some(msg) -> reason <> ": " <> msg
33+
_ -> reason
34+
}
35+
}

src/go_over/warning.gleam

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import gleam/hexpm.{type ReleaseRetirement}
2+
import gleam/list
3+
import gleam/string
4+
import go_over/advisories.{type ADV}
5+
import go_over/packages.{type Package}
6+
import go_over/retired
7+
8+
pub type WarningReasonCode {
9+
Retired
10+
Vulnerable
11+
}
12+
13+
fn warning_reason_code_as_string(w: WarningReasonCode) {
14+
case w {
15+
Retired -> "Retired"
16+
Vulnerable -> "Vulnerable"
17+
}
18+
}
19+
20+
pub type Dep {
21+
Direct
22+
Indirect(of: String)
23+
}
24+
25+
fn dep_code_as_string(d: Dep) {
26+
case d {
27+
Direct -> "Direct"
28+
Indirect(p) -> "Indirect dependency of " <> p
29+
}
30+
}
31+
32+
pub type Warning {
33+
Warning(
34+
package: String,
35+
version: String,
36+
reason: String,
37+
warning_reason_code: WarningReasonCode,
38+
dep: Dep,
39+
)
40+
}
41+
42+
pub fn adv_to_warning(pkg: Package, adv: List(ADV)) {
43+
Warning(
44+
pkg.name,
45+
pkg.version_raw,
46+
list.map(adv, advisories.print_adv)
47+
|> string.join("\n"),
48+
Vulnerable,
49+
Direct,
50+
)
51+
}
52+
53+
pub fn retired_to_warning(pkg: Package, ret: ReleaseRetirement) {
54+
Warning(pkg.name, pkg.version_raw, retired.print_ret(ret), Retired, Direct)
55+
}
56+
57+
pub fn print(w: Warning) {
58+
[
59+
"PACKAGE: ",
60+
w.package,
61+
"VERSION: ",
62+
w.version,
63+
"REASON: ",
64+
w.reason,
65+
"warningReasonCode: ",
66+
warning_reason_code_as_string(w.warning_reason_code),
67+
"Dep: ",
68+
dep_code_as_string(w.dep),
69+
]
70+
|> string.join("\n")
71+
}

src/go_over/yaml.gleam

-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,3 @@ pub fn start() {
77

88
@external(erlang, "yamll", "parse")
99
pub fn parse(path: String) -> #(String, List(String))
10-
11-
12-

src/yamll.erl

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
parse/1
55
]).
66

7-
87
parse(Path) ->
98
[Content] = yamerl:decode_file(Path),
109
Name = lists:keyfind("package", 1, Content),

0 commit comments

Comments
 (0)