Skip cooldown diagnostics in read-only checks (#204) · github/gh-actions-lock@5517160 · GitHub
Skip to content

Commit 5517160

Browse files
Skip cooldown diagnostics in read-only checks (#204)
* Skip cooldown diagnostics in read-only checks Load cooldown policy only for fix runs, where it can affect pin selection. Preserve ignored-config warnings for resolution and relocking. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Align read-only cooldown integration expectations Require warning absence while preserving unpinned-action findings and the failing exit status. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent d549b67 commit 5517160

4 files changed

Lines changed: 116 additions & 7 deletions

File tree

‎cmd/gh-actions-lock/run.go‎

Lines changed: 4 additions & 4 deletions

‎cmd/gh-actions-lock/run_test.go‎

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,122 @@
11
package main
22

33
import (
4+
"encoding/json"
5+
"fmt"
46
"net/http"
7+
"os"
8+
"path/filepath"
59
"strings"
610
"testing"
711

12+
"github.com/github/gh-actions-lock/cmd/gh-actions-lock/format"
813
"github.com/github/gh-actions-lock/internal/ghapi/httpmock"
914
"github.com/stretchr/testify/assert"
1015
"github.com/stretchr/testify/require"
1116
)
1217

18+
func TestRun_CooldownWarningsOnlyOnFixRuns(t *testing.T) {
19+
for _, mode := range []struct {
20+
name string
21+
args []string
22+
readOnly bool
23+
}{
24+
{"resolve", nil, false},
25+
{"relock", []string{"--relock"}, false},
26+
{"accept-moved", []string{"--accept-moved"}, false},
27+
{"no-fix", []string{"--no-fix"}, true},
28+
{"verify", []string{"--verify"}, true},
29+
{"verify-local", []string{"--verify-local"}, true},
30+
{"no-fix-relock", []string{"--no-fix", "--relock"}, true},
31+
{"verify-relock", []string{"--verify", "--relock"}, true},
32+
{"no-fix-accept-moved", []string{"--no-fix", "--accept-moved"}, true},
33+
} {
34+
for _, jsonOut := range []bool{false, true} {
35+
t.Run(fmt.Sprintf("%s/json=%v", mode.name, jsonOut), func(t *testing.T) {
36+
const sha = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
37+
pins := []string{}
38+
if mode.readOnly {
39+
pins = append(pins, "example/action@main=sha1-"+sha)
40+
}
41+
path := writeTempWorkflow(t, `
42+
name: ci
43+
on: push
44+
jobs:
45+
test:
46+
runs-on: ubuntu-latest
47+
steps:
48+
- uses: example/action@main
49+
`, pins...)
50+
t.Setenv("GH_ACTIONS_LOCK_CONFIG", filepath.Join(t.TempDir(), "none.yml"))
51+
require.NoError(t, os.WriteFile(filepath.Join(".github", "dependabot.yml"), []byte(`
52+
version: 2
53+
updates:
54+
- package-ecosystem: github-actions
55+
directory: /
56+
cooldown:
57+
default-days: 7
58+
exclude: ["example/*"]
59+
`), 0o644))
60+
before, err := os.ReadFile(path)
61+
require.NoError(t, err)
62+
lockBefore := ""
63+
if mode.readOnly {
64+
lockBefore = readTempLockfilePins(t)
65+
}
66+
67+
reg := &httpmock.Registry{}
68+
if mode.name != "verify-local" {
69+
reg.Register(httpmock.GraphQLForRepo("example", "action"), httpmock.JSONResponse(map[string]any{
70+
"data": map[string]any{"a0": testRepoResponse("example/action", sha, nodeActionYAML)},
71+
}))
72+
}
73+
if !mode.readOnly {
74+
reg.Register(httpmock.REST("GET", "repos/example/action$"), httpmock.JSONResponse(map[string]any{
75+
"id": 2, "owner": map[string]any{"id": 1}, "default_branch": "main",
76+
}))
77+
}
78+
args := append([]string{"--no-interactive"}, mode.args...)
79+
if jsonOut {
80+
args = append(args, "--json=valid,findings")
81+
}
82+
stdout, stderr, err := runCommandWithHTTP(t, reg, append(args, path)...)
83+
require.NoError(t, err, "stdout:\n%s\nstderr:\n%s", stdout, stderr)
84+
const warning = "Dependabot cooldown include/exclude filters are not supported and were ignored"
85+
if jsonOut {
86+
var payload struct {
87+
Valid bool `json:"valid"`
88+
Findings []format.Finding `json:"findings"`
89+
}
90+
require.NoError(t, json.Unmarshal([]byte(stdout), &payload))
91+
if mode.readOnly {
92+
assert.True(t, payload.Valid)
93+
assert.Empty(t, payload.Findings)
94+
} else {
95+
require.Len(t, payload.Findings, 2)
96+
assert.Equal(t, "cooldown-config-ignored", payload.Findings[0].Category)
97+
assert.Equal(t, "warning", payload.Findings[0].Severity)
98+
assert.Equal(t, warning, payload.Findings[0].Detail)
99+
assert.Equal(t, "not-pinned", payload.Findings[1].Category)
100+
}
101+
} else if mode.readOnly {
102+
assert.NotContains(t, stdout+stderr, warning)
103+
} else {
104+
assert.Contains(t, stdout+stderr, warning)
105+
}
106+
if mode.readOnly {
107+
after, err := os.ReadFile(path)
108+
require.NoError(t, err)
109+
assert.Equal(t, before, after)
110+
assert.Equal(t, lockBefore, readTempLockfilePins(t))
111+
} else {
112+
assert.Contains(t, readTempLockfilePins(t), sha)
113+
}
114+
reg.Verify(t)
115+
})
116+
}
117+
}
118+
}
119+
13120
func TestRun_ParitySSOGuidance(t *testing.T) {
14121
const message = "Resource protected by organization SAML enforcement. You must grant your Personal Access token access to this organization."
15122
for _, mode := range []struct {

‎test/integration/run.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ def wire_checkout_fresh(s, token)
666666
fresh_tag_zero_days_still_warns: ->(s) {
667667
wire_checkout_fresh(s, "gho_fake_fresh_zero_token")
668668
},
669-
cooldown_ignored_keys_surface_in_readonly: ->(s) {
669+
cooldown_ignored_keys_suppressed_in_readonly: ->(s) {
670670
wire_checkout_success(s, "gho_fake_cooldown_ignored_token")
671671
},
672672
migrate_local_actions_rewrite: ->(s) {

‎test/scenarios/catalog.yml‎

Lines changed: 4 additions & 2 deletions

0 commit comments

Comments
 (0)