Skip to content

Commit c13c927

Browse files
authored
Feat: Reading usns from filepath (#1303)
* fix: Adding functionality for reading usns from a file * fix: action is being read from a Dockerfile * fix: removing extra parameter
1 parent c4453d9 commit c13c927

4 files changed

Lines changed: 108 additions & 25 deletions

File tree

actions/stack/get-usns/README.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

actions/stack/get-usns/action.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ inputs:
1616
last_usns_filepath:
1717
description: 'Similar to last_usns, but instead of pointing into a variable, it points to a file'
1818
required: false
19+
api_url:
20+
description: 'URL of the Ubuntu security notices JSON API (https or file:// for a local JSON file)'
21+
required: false
22+
default: 'https://ubuntu.com/security/notices.json'
1923
packages:
2024
description: 'JSON array of stack package names'
2125
required: false
@@ -29,15 +33,21 @@ inputs:
2933
usns_output_path:
3034
description: 'Path to output usns JSON file'
3135
required: false
36+
pages:
37+
description: 'Number of pages to fetch from the API (each page has 20 notices)'
38+
required: false
39+
default: '1'
3240

3341
runs:
3442
using: 'docker'
35-
image: 'docker://ghcr.io/paketo-buildpacks/actions/stack/get-usns:latest'
43+
image: 'Dockerfile'
3644
args:
3745
- "--last-usns"
3846
- "${{ inputs.last_usns }}"
3947
- "--last-usns-filepath"
4048
- "${{ inputs.last_usns_filepath }}"
49+
- "--api-url"
50+
- "${{ inputs.api_url }}"
4151
- "--packages"
4252
- "${{ inputs.packages }}"
4353
- "--packages-filepath"
@@ -46,3 +56,5 @@ runs:
4656
- "${{ inputs.distribution }}"
4757
- "--output"
4858
- "${{ inputs.usns_output_path }}"
59+
- "--pages"
60+
- "${{ inputs.pages }}"

actions/stack/get-usns/entrypoint/main.go

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import (
77
"io"
88
"log"
99
"net/http"
10+
"net/url"
1011
"os"
1112
"path/filepath"
1213
"slices"
14+
"strings"
1315
"time"
1416
)
1517

@@ -47,13 +49,14 @@ type PatchedUsnsInputOutput struct {
4749

4850
func main() {
4951
var config struct {
52+
APIUrl string
5053
Distro string
5154
LastUSNsJSON string
5255
LastUSNsJSONFilepath string
5356
Output string
5457
PackagesJSON string
5558
PackagesJSONFilepath string
56-
APIUrl string
59+
Pages int
5760
}
5861

5962
flag.StringVar(&config.LastUSNsJSON,
@@ -67,7 +70,7 @@ func main() {
6770
flag.StringVar(&config.APIUrl,
6871
"api-url",
6972
JSON_API_URL,
70-
"URL of the Ubuntu security notices JSON API")
73+
"URL of the Ubuntu security notices JSON API (https or file:// for a local JSON file)")
7174
flag.StringVar(&config.PackagesJSON,
7275
"packages",
7376
"",
@@ -84,12 +87,19 @@ func main() {
8487
"output",
8588
"",
8689
"Path to output JSON file")
90+
flag.IntVar(&config.Pages,
91+
"pages",
92+
1,
93+
"Number of pages to fetch from the API (default: 1)")
8794

8895
flag.Parse()
8996

9097
if !slices.Contains(supportedDistros, config.Distro) {
9198
log.Fatalf("--distro flag has to be one of the following values: %v", supportedDistros)
9299
}
100+
if config.Pages < 1 {
101+
log.Fatalf("--pages must be at least 1, got %d", config.Pages)
102+
}
93103

94104
lastPatchedUSNs := []PatchedUsnsInputOutput{}
95105
if config.LastUSNsJSON != "" {
@@ -132,9 +142,23 @@ func main() {
132142
}
133143
}
134144

135-
newUSNs, err := getNewUSNsFromJSONApi(config.APIUrl, lastPatchedUSNs, config.Distro)
136-
if err != nil {
137-
log.Fatal(err)
145+
var newUSNs []USN
146+
var err error
147+
if path, ok := fileURLPath(config.APIUrl); ok {
148+
newUSNs, err = getNewUSNsFromFilepath(path)
149+
if err != nil {
150+
log.Fatal(err)
151+
}
152+
} else {
153+
newUSNs, err = getNewUSNsFromJSONApi(config.APIUrl, lastPatchedUSNs, config.Distro, config.Pages)
154+
if err != nil {
155+
log.Fatal(err)
156+
}
157+
}
158+
159+
fmt.Println("Recent USNs found:")
160+
for _, usn := range newUSNs {
161+
fmt.Printf("%s with name %s\n", usn.ID, usn.Title)
138162
}
139163

140164
filteredUSNs := filterUSNsByPackages(newUSNs, packages, config.Distro)
@@ -225,20 +249,48 @@ func transformUSNsForOutput(usns []USN, distro string) []PatchedUsnsInputOutput
225249
return output
226250
}
227251

228-
func getNewUSNsFromJSONApi(jsonApiUrl string, lastPatchedUSNs []PatchedUsnsInputOutput, distro string) ([]USN, error) {
252+
func fileURLPath(rawURL string) (string, bool) {
253+
u, err := url.Parse(strings.TrimSpace(rawURL))
254+
if err != nil || u.Scheme != "file" {
255+
return "", false
256+
}
257+
path := u.Path
258+
if u.Host != "" {
259+
path = u.Host + path
260+
}
261+
return path, true
262+
}
263+
264+
func getNewUSNsFromFilepath(path string) ([]USN, error) {
265+
fileContent, err := os.ReadFile(path)
266+
if err != nil {
267+
return nil, err
268+
}
269+
270+
var data struct {
271+
Notices []USN `json:"notices"`
272+
}
273+
if err = json.Unmarshal(fileContent, &data); err != nil {
274+
return nil, err
275+
}
276+
return data.Notices, nil
277+
}
278+
279+
const pageSize = 20
280+
281+
func getNewUSNsFromJSONApi(jsonApiUrl string, lastPatchedUSNs []PatchedUsnsInputOutput, distro string, numPages int) ([]USN, error) {
229282
var allUSNs []USN
230283

231-
offsets := []int{0, 20}
232-
for _, offset := range offsets {
233-
paginatedUrl := fmt.Sprintf("%s?release=%s&limit=%d&offset=%d", jsonApiUrl, distro, 20, offset)
284+
for page := 0; page < numPages; page++ {
285+
offset := page * pageSize
286+
paginatedUrl := fmt.Sprintf("%s?release=%s&limit=%d&offset=%d", jsonApiUrl, distro, pageSize, offset)
234287
usns, err := fetchUSNPage(paginatedUrl)
235288
if err != nil {
236289
return nil, err
237290
}
238291
allUSNs = append(allUSNs, usns...)
239292
}
240293

241-
fmt.Println("Looking for new USNs...")
242294
var newUSNs []USN
243295
for _, usn := range allUSNs {
244296

@@ -254,14 +306,6 @@ func getNewUSNsFromJSONApi(jsonApiUrl string, lastPatchedUSNs []PatchedUsnsInput
254306
})
255307
}
256308

257-
if len(newUSNs) > 0 {
258-
for _, usn := range newUSNs {
259-
fmt.Printf("New USN found: %s with name %s\n", usn.ID, usn.Title)
260-
}
261-
} else {
262-
fmt.Println("No new USNs found")
263-
}
264-
265309
return newUSNs, nil
266310
}
267311

actions/stack/get-usns/entrypoint/main_test.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,38 @@ func TestEntrypoint(t *testing.T) {
8787
Expect(err).NotTo(HaveOccurred())
8888

8989
Eventually(session).Should(gexec.Exit(0), func() string { return fmt.Sprintf("output -> \n%s\n", buffer.Contents()) })
90-
Expect(string(buffer.Contents())).To(ContainSubstring("New USN found:"))
90+
Expect(string(buffer.Contents())).To(ContainSubstring("Recent USNs found:"))
91+
92+
Expect(outputFilepath).To(BeARegularFile())
93+
contents, err := os.ReadFile(outputFilepath)
94+
Expect(err).NotTo(HaveOccurred())
95+
96+
Expect(string(contents)).To(ContainSubstring(`"id":"USN-7967-1"`))
97+
Expect(string(contents)).To(ContainSubstring(`"avahi"`))
98+
})
99+
})
100+
101+
context("Fetches usns from a file (file://) and previous usns are empty", func() {
102+
it("outputs the correct patched usns", func() {
103+
testdataPath, err := filepath.Abs("testdata/notices0-20.json")
104+
Expect(err).NotTo(HaveOccurred())
105+
apiURL := "file://" + testdataPath
106+
107+
command := exec.Command(
108+
entrypoint,
109+
"--api-url", apiURL,
110+
"--packages", `["avahi", "simgear"]`,
111+
"--distro", "noble",
112+
"--output", outputFilepath,
113+
)
114+
115+
buffer := gbytes.NewBuffer()
116+
117+
session, err := gexec.Start(command, buffer, buffer)
118+
Expect(err).NotTo(HaveOccurred())
119+
120+
Eventually(session).Should(gexec.Exit(0), func() string { return fmt.Sprintf("output -> \n%s\n", buffer.Contents()) })
121+
Expect(string(buffer.Contents())).To(ContainSubstring("Recent USNs found:"))
91122

92123
Expect(outputFilepath).To(BeARegularFile())
93124
contents, err := os.ReadFile(outputFilepath)
@@ -156,6 +187,7 @@ func TestEntrypoint(t *testing.T) {
156187
"--distro", "jammy",
157188
"--last-usns-filepath", "testdata/amd64-jammy-tiny-last-patched-usns.json",
158189
"--output", outputFilepath,
190+
"--pages", "2",
159191
)
160192

161193
buffer := gbytes.NewBuffer()

0 commit comments

Comments
 (0)