From 6355e54e3cc1becd3a454ea5f0508f27f64b849a Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Fri, 21 Mar 2025 11:51:03 -0400 Subject: [PATCH 01/22] Ensure table headers are thematically contrasting This commit refactors the color format around table headers to ensure the GitHub CLI uses thematically appropriate colors based on dark background, light background, or no color at all. In order to do so, `ColorScheme` needs information from the terminal about the background appearance (dark, light, none) to determine appropriate muted color. --- internal/tableprinter/table_printer.go | 2 +- pkg/cmd/gist/list/list_test.go | 2 +- pkg/iostreams/color.go | 61 +++++++++++++++++--------- pkg/iostreams/color_test.go | 16 +++---- pkg/iostreams/iostreams.go | 2 +- 5 files changed, 51 insertions(+), 32 deletions(-) diff --git a/internal/tableprinter/table_printer.go b/internal/tableprinter/table_printer.go index e1454170f..69b22be12 100644 --- a/internal/tableprinter/table_printer.go +++ b/internal/tableprinter/table_printer.go @@ -80,7 +80,7 @@ func NewWithWriter(w io.Writer, isTTY bool, maxWidth int, cs *iostreams.ColorSch tp.AddHeader( upperCasedHeaders, WithPadding(paddingFunc), - WithColor(cs.LightGrayUnderline), + WithColor(cs.TableHeader), ) } diff --git a/pkg/cmd/gist/list/list_test.go b/pkg/cmd/gist/list/list_test.go index 0acfae109..870f58147 100644 --- a/pkg/cmd/gist/list/list_test.go +++ b/pkg/cmd/gist/list/list_test.go @@ -694,7 +694,7 @@ func Test_highlightMatch(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cs := iostreams.NewColorScheme(tt.color, false, false) + cs := iostreams.NewColorScheme(tt.color, false, false, iostreams.NoTheme) matched := false got, err := highlightMatch(tt.input, regex, &matched, cs.Blue, cs.Highlight) diff --git a/pkg/iostreams/color.go b/pkg/iostreams/color.go index c8d48168f..f97d1f93e 100644 --- a/pkg/iostreams/color.go +++ b/pkg/iostreams/color.go @@ -9,34 +9,44 @@ import ( ) const ( + NoTheme = "none" + DarkTheme = "dark" + LightTheme = "light" highlightStyle = "black:yellow" ) +// Special cases like darkTableHeader / lightTableHeader are necessary when using color and modifiers +// (bold, underline, dim) because ansi.ColorFunc requires a foreground color and resets formats. var ( - magenta = ansi.ColorFunc("magenta") - cyan = ansi.ColorFunc("cyan") - red = ansi.ColorFunc("red") - yellow = ansi.ColorFunc("yellow") - blue = ansi.ColorFunc("blue") - green = ansi.ColorFunc("green") - gray = ansi.ColorFunc("black+h") - lightGrayUnderline = ansi.ColorFunc("white+du") - bold = ansi.ColorFunc("default+b") - cyanBold = ansi.ColorFunc("cyan+b") - greenBold = ansi.ColorFunc("green+b") - highlightStart = ansi.ColorCode(highlightStyle) - highlight = ansi.ColorFunc(highlightStyle) + magenta = ansi.ColorFunc("magenta") + cyan = ansi.ColorFunc("cyan") + red = ansi.ColorFunc("red") + yellow = ansi.ColorFunc("yellow") + blue = ansi.ColorFunc("blue") + green = ansi.ColorFunc("green") + gray = ansi.ColorFunc("black+h") + bold = ansi.ColorFunc("default+b") + cyanBold = ansi.ColorFunc("cyan+b") + greenBold = ansi.ColorFunc("green+b") + highlightStart = ansi.ColorCode(highlightStyle) + highlight = ansi.ColorFunc(highlightStyle) + darkTableHeader = ansi.ColorFunc("white+du") + lightTableHeader = ansi.ColorFunc("black+hu") gray256 = func(t string) string { return fmt.Sprintf("\x1b[%d;5;%dm%s\x1b[m", 38, 242, t) } ) -func NewColorScheme(enabled, is256enabled bool, trueColor bool) *ColorScheme { +// NewColorScheme initializes color logic based on provided terminal capabilities. +// Logic dealing with terminal theme detected, such as whether color is enabled, 8-bit color supported, true color supported, +// and terminal theme detected. +func NewColorScheme(enabled, is256enabled, trueColor bool, theme string) *ColorScheme { return &ColorScheme{ enabled: enabled, is256enabled: is256enabled, hasTrueColor: trueColor, + theme: theme, } } @@ -44,6 +54,7 @@ type ColorScheme struct { enabled bool is256enabled bool hasTrueColor bool + theme string } func (c *ColorScheme) Enabled() bool { @@ -115,13 +126,6 @@ func (c *ColorScheme) Grayf(t string, args ...interface{}) string { return c.Gray(fmt.Sprintf(t, args...)) } -func (c *ColorScheme) LightGrayUnderline(t string) string { - if !c.enabled { - return t - } - return lightGrayUnderline(t) -} - func (c *ColorScheme) Magenta(t string) string { if !c.enabled { return t @@ -254,3 +258,18 @@ func (c *ColorScheme) HexToRGB(hex string, x string) string { b, _ := strconv.ParseInt(hex[4:6], 16, 64) return fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", r, g, b, x) } + +func (c *ColorScheme) TableHeader(t string) string { + if !c.enabled { + return t + } + + switch c.theme { + case DarkTheme: + return darkTableHeader(t) + case LightTheme: + return lightTableHeader(t) + default: + return t + } +} diff --git a/pkg/iostreams/color_test.go b/pkg/iostreams/color_test.go index 59fea53ed..9c84f72e1 100644 --- a/pkg/iostreams/color_test.go +++ b/pkg/iostreams/color_test.go @@ -19,28 +19,28 @@ func TestColorFromRGB(t *testing.T) { hex: "fc0303", text: "red", wants: "\033[38;2;252;3;3mred\033[0m", - cs: NewColorScheme(true, true, true), + cs: NewColorScheme(true, true, true, NoTheme), }, { name: "no truecolor", hex: "fc0303", text: "red", wants: "red", - cs: NewColorScheme(true, true, false), + cs: NewColorScheme(true, true, false, NoTheme), }, { name: "no color", hex: "fc0303", text: "red", wants: "red", - cs: NewColorScheme(false, false, false), + cs: NewColorScheme(false, false, false, NoTheme), }, { name: "invalid hex", hex: "fc0", text: "red", wants: "red", - cs: NewColorScheme(false, false, false), + cs: NewColorScheme(false, false, false, NoTheme), }, } @@ -63,28 +63,28 @@ func TestHexToRGB(t *testing.T) { hex: "fc0303", text: "red", wants: "\033[38;2;252;3;3mred\033[0m", - cs: NewColorScheme(true, true, true), + cs: NewColorScheme(true, true, true, NoTheme), }, { name: "no truecolor", hex: "fc0303", text: "red", wants: "red", - cs: NewColorScheme(true, true, false), + cs: NewColorScheme(true, true, false, NoTheme), }, { name: "no color", hex: "fc0303", text: "red", wants: "red", - cs: NewColorScheme(false, false, false), + cs: NewColorScheme(false, false, false, NoTheme), }, { name: "invalid hex", hex: "fc0", text: "red", wants: "red", - cs: NewColorScheme(false, false, false), + cs: NewColorScheme(false, false, false, NoTheme), }, } diff --git a/pkg/iostreams/iostreams.go b/pkg/iostreams/iostreams.go index 2bc712a3c..6c12d7911 100644 --- a/pkg/iostreams/iostreams.go +++ b/pkg/iostreams/iostreams.go @@ -366,7 +366,7 @@ func (s *IOStreams) TerminalWidth() int { } func (s *IOStreams) ColorScheme() *ColorScheme { - return NewColorScheme(s.ColorEnabled(), s.ColorSupport256(), s.HasTrueColor()) + return NewColorScheme(s.ColorEnabled(), s.ColorSupport256(), s.HasTrueColor(), s.TerminalTheme()) } func (s *IOStreams) ReadUserFile(fn string) ([]byte, error) { From 736ce69f66c69a974dd5b489ea918b3da5a4836a Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Fri, 21 Mar 2025 12:27:09 -0400 Subject: [PATCH 02/22] Underline table headers if colors enabled but no theme This enhances the table header stylizing logic to ensure they are underlined if color is enabled but no theme is desired. --- pkg/iostreams/color.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/iostreams/color.go b/pkg/iostreams/color.go index f97d1f93e..e2841d030 100644 --- a/pkg/iostreams/color.go +++ b/pkg/iostreams/color.go @@ -32,6 +32,7 @@ var ( highlight = ansi.ColorFunc(highlightStyle) darkTableHeader = ansi.ColorFunc("white+du") lightTableHeader = ansi.ColorFunc("black+hu") + noneTableHeader = ansi.ColorFunc("default+u") gray256 = func(t string) string { return fmt.Sprintf("\x1b[%d;5;%dm%s\x1b[m", 38, 242, t) @@ -260,6 +261,7 @@ func (c *ColorScheme) HexToRGB(hex string, x string) string { } func (c *ColorScheme) TableHeader(t string) string { + // Table headers are only stylized if color is enabled including underline modifier. if !c.enabled { return t } @@ -270,6 +272,6 @@ func (c *ColorScheme) TableHeader(t string) string { case LightTheme: return lightTableHeader(t) default: - return t + return noneTableHeader(t) } } From 5817f6fce92b36b935c8d7653e1abe51f9cae17d Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 24 Mar 2025 12:34:50 -0600 Subject: [PATCH 03/22] fix(run list): do not fail on org workflows --- pkg/cmd/run/list/list_test.go | 38 +++++++++++++++++++++++++++++++++++ pkg/cmd/run/shared/shared.go | 10 +++++++++ pkg/cmd/run/shared/test.go | 16 +++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/pkg/cmd/run/list/list_test.go b/pkg/cmd/run/list/list_test.go index 7717c2ff9..e41809bf1 100644 --- a/pkg/cmd/run/list/list_test.go +++ b/pkg/cmd/run/list/list_test.go @@ -366,6 +366,44 @@ func TestListRun(t *testing.T) { completed stale cool commit CI trunk push 10 4m34s 2021-02-23T04:51:00Z `), }, + { + name: "organization required workflow in run list (workflow GET returns 404)", + opts: &ListOptions{ + Limit: defaultLimit, + now: shared.TestRunStartTime.Add(time.Minute*4 + time.Second*34), + }, + isTTY: true, + stubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"), + httpmock.JSONResponse(shared.RunsPayload{ + WorkflowRuns: shared.TestRunsWithOrgRequiredWorkflows, + })) + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"), + httpmock.JSONResponse(workflowShared.WorkflowsPayload{ + Workflows: []workflowShared.Workflow{ + shared.TestWorkflow, + }, + })) + reg.Register( + httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/456"), + httpmock.StatusStringResponse(404, "not found"), + ) + }, + wantOut: heredoc.Doc(` + STATUS TITLE WORKFLOW BRANCH EVENT ID ELAPSED AGE + X cool commit trunk push 1 4m34s about 4 minutes ago + * cool commit trunk push 2 4m34s about 4 minutes ago + ✓ cool commit trunk push 3 4m34s about 4 minutes ago + X cool commit trunk push 4 4m34s about 4 minutes ago + X cool commit CI trunk push 5 4m34s about 4 minutes ago + - cool commit CI trunk push 6 4m34s about 4 minutes ago + - cool commit CI trunk push 7 4m34s about 4 minutes ago + * cool commit CI trunk push 8 4m34s about 4 minutes ago + * cool commit CI trunk push 9 4m34s about 4 minutes ago + `), + }, { name: "pagination", opts: &ListOptions{ diff --git a/pkg/cmd/run/shared/shared.go b/pkg/cmd/run/shared/shared.go index 040888ab5..b35f0c462 100644 --- a/pkg/cmd/run/shared/shared.go +++ b/pkg/cmd/run/shared/shared.go @@ -446,6 +446,16 @@ func preloadWorkflowNames(client *api.Client, repo ghrepo.Interface, runs []Run) if _, ok := workflowMap[run.WorkflowID]; !ok { // Look up workflow by ID because it may have been deleted workflow, err := workflowShared.GetWorkflow(client, repo, run.WorkflowID) + // If the error is an httpError and it is a 404, this is likely a + // organization-level "required workflow" ruleset. The user does not + // have permissions to view the details of the workflow, so we cannot + // look it up directly without receiving a 404, but it is nonetheless + // in the workflow run list. To handle this, we set the workflow name + // to an empty string. + if httpErr, ok := err.(api.HTTPError); ok && httpErr.StatusCode == 404 { + workflowMap[run.WorkflowID] = "" + continue + } if err != nil { return err } diff --git a/pkg/cmd/run/shared/test.go b/pkg/cmd/run/shared/test.go index 7caa37039..0619541a4 100644 --- a/pkg/cmd/run/shared/test.go +++ b/pkg/cmd/run/shared/test.go @@ -18,6 +18,10 @@ func TestRunWithCommit(id int64, s Status, c Conclusion, commit string) Run { return TestRunWithWorkflowAndCommit(123, id, s, c, commit) } +func TestRunWithOrgRequiredWorkflow(id int64, s Status, c Conclusion, commit string) Run { + return TestRunWithWorkflowAndCommit(456, id, s, c, commit) +} + func TestRunWithWorkflowAndCommit(workflowId, runId int64, s Status, c Conclusion, commit string) Run { return Run{ WorkflowID: workflowId, @@ -57,6 +61,18 @@ var TestRuns []Run = []Run{ TestRun(10, Completed, Stale), } +var TestRunsWithOrgRequiredWorkflows []Run = []Run{ + TestRunWithOrgRequiredWorkflow(1, Completed, TimedOut, "cool commit"), + TestRunWithOrgRequiredWorkflow(2, InProgress, "", "cool commit"), + TestRunWithOrgRequiredWorkflow(3, Completed, Success, "cool commit"), + TestRunWithOrgRequiredWorkflow(4, Completed, Cancelled, "cool commit"), + TestRun(5, Completed, Failure), + TestRun(6, Completed, Neutral), + TestRun(7, Completed, Skipped), + TestRun(8, Requested, ""), + TestRun(9, Queued, ""), +} + var WorkflowRuns []Run = []Run{ TestRun(2, InProgress, ""), SuccessfulRun, From d3ad4f410b97a1a01ddefe9c3ead5f41c0808e52 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 24 Mar 2025 12:45:03 -0600 Subject: [PATCH 04/22] docs(run list): doc runs without workflow names --- pkg/cmd/run/list/list.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/cmd/run/list/list.go b/pkg/cmd/run/list/list.go index 3113fdabd..adf0aeeb5 100644 --- a/pkg/cmd/run/list/list.go +++ b/pkg/cmd/run/list/list.go @@ -62,6 +62,9 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman Note that providing the %[1]sworkflow_name%[1]s to the %[1]s-w%[1]s flag will not fetch disabled workflows. Also pass the %[1]s-a%[1]s flag to fetch disabled workflow runs using the %[1]sworkflow_name%[1]s and the %[1]s-w%[1]s flag. + + A run with no workflow name indicates that the run likely belongs an organization ruleset required workflow, + and the authenticated user does not have access to the workflow definition. `, "`"), Aliases: []string{"ls"}, Args: cobra.NoArgs, From a35ae3b8bb5648d7908abfe89deab318abc5e5af Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 24 Mar 2025 12:54:47 -0600 Subject: [PATCH 05/22] refactor(tests): update test name for org workflows --- pkg/cmd/run/list/list_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/run/list/list_test.go b/pkg/cmd/run/list/list_test.go index e41809bf1..1215bddc9 100644 --- a/pkg/cmd/run/list/list_test.go +++ b/pkg/cmd/run/list/list_test.go @@ -367,7 +367,7 @@ func TestListRun(t *testing.T) { `), }, { - name: "organization required workflow in run list (workflow GET returns 404)", + name: "org required workflow in runs list shows with empty workflow name", opts: &ListOptions{ Limit: defaultLimit, now: shared.TestRunStartTime.Add(time.Minute*4 + time.Second*34), From 30960d59bc011936482128d58ebb18c4aaf95980 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:04:16 -0600 Subject: [PATCH 06/22] doc(run list): add enterprise ruleset notes Co-authored-by: Andy Feller --- pkg/cmd/run/list/list.go | 3 +-- pkg/cmd/run/shared/shared.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/run/list/list.go b/pkg/cmd/run/list/list.go index adf0aeeb5..7b18c391d 100644 --- a/pkg/cmd/run/list/list.go +++ b/pkg/cmd/run/list/list.go @@ -63,8 +63,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman Note that providing the %[1]sworkflow_name%[1]s to the %[1]s-w%[1]s flag will not fetch disabled workflows. Also pass the %[1]s-a%[1]s flag to fetch disabled workflow runs using the %[1]sworkflow_name%[1]s and the %[1]s-w%[1]s flag. - A run with no workflow name indicates that the run likely belongs an organization ruleset required workflow, - and the authenticated user does not have access to the workflow definition. + Runs created by organization and enterprise ruleset workflows will not display a workflow name due to GitHub API limitations. `, "`"), Aliases: []string{"ls"}, Args: cobra.NoArgs, diff --git a/pkg/cmd/run/shared/shared.go b/pkg/cmd/run/shared/shared.go index b35f0c462..248b5c5a0 100644 --- a/pkg/cmd/run/shared/shared.go +++ b/pkg/cmd/run/shared/shared.go @@ -447,7 +447,7 @@ func preloadWorkflowNames(client *api.Client, repo ghrepo.Interface, runs []Run) // Look up workflow by ID because it may have been deleted workflow, err := workflowShared.GetWorkflow(client, repo, run.WorkflowID) // If the error is an httpError and it is a 404, this is likely a - // organization-level "required workflow" ruleset. The user does not + // organization or enterprise ruleset workflow. The user does not // have permissions to view the details of the workflow, so we cannot // look it up directly without receiving a 404, but it is nonetheless // in the workflow run list. To handle this, we set the workflow name From c6f574ccb19fa4d8819c3554e806eb07f87a7ebe Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:14:50 -0600 Subject: [PATCH 07/22] test(run list): update rulesets test name Co-authored-by: Andy Feller --- pkg/cmd/run/list/list_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/run/list/list_test.go b/pkg/cmd/run/list/list_test.go index 1215bddc9..0f11e492a 100644 --- a/pkg/cmd/run/list/list_test.go +++ b/pkg/cmd/run/list/list_test.go @@ -367,7 +367,7 @@ func TestListRun(t *testing.T) { `), }, { - name: "org required workflow in runs list shows with empty workflow name", + name: "org ruleset workflow in runs list shows with empty workflow name", opts: &ListOptions{ Limit: defaultLimit, now: shared.TestRunStartTime.Add(time.Minute*4 + time.Second*34), From 79f1b07fb1bc4b144098ae570008662e429f1990 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Tue, 25 Mar 2025 15:29:05 -0400 Subject: [PATCH 08/22] Implement and fix tests for table headers --- pkg/cmd/gist/list/list_test.go | 2 +- pkg/iostreams/color_test.go | 82 ++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/gist/list/list_test.go b/pkg/cmd/gist/list/list_test.go index 870f58147..4f6c8a9f7 100644 --- a/pkg/cmd/gist/list/list_test.go +++ b/pkg/cmd/gist/list/list_test.go @@ -486,7 +486,7 @@ func Test_listRun(t *testing.T) { ) }, wantOut: heredoc.Docf(` - %[1]s[0;2;4;37mID %[1]s[0m %[1]s[0;2;4;37mDESCRIPTION %[1]s[0m %[1]s[0;2;4;37mFILES %[1]s[0m %[1]s[0;2;4;37mVISIBILITY%[1]s[0m %[1]s[0;2;4;37mUPDATED %[1]s[0m + %[1]s[0;4;39mID %[1]s[0m %[1]s[0;4;39mDESCRIPTION %[1]s[0m %[1]s[0;4;39mFILES %[1]s[0m %[1]s[0;4;39mVISIBILITY%[1]s[0m %[1]s[0;4;39mUPDATED %[1]s[0m 1234 %[1]s[0;30;43mocto%[1]s[0m%[1]s[0;1;39m match in the description%[1]s[0m 1 file %[1]s[0;32mpublic %[1]s[0m %[1]s[38;5;242mabout 6 hours ago%[1]s[m 2345 %[1]s[0;1;39mmatch in the file name %[1]s[0m %[1]s[0;30;43m2 files%[1]s[0m %[1]s[0;31msecret %[1]s[0m %[1]s[38;5;242mabout 6 hours ago%[1]s[m `, "\x1b"), diff --git a/pkg/iostreams/color_test.go b/pkg/iostreams/color_test.go index 9c84f72e1..b35c2eb73 100644 --- a/pkg/iostreams/color_test.go +++ b/pkg/iostreams/color_test.go @@ -1,6 +1,7 @@ package iostreams import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -93,3 +94,84 @@ func TestHexToRGB(t *testing.T) { assert.Equal(t, tt.wants, output) } } + +func TestTableHeader(t *testing.T) { + reset := "\x1b[0m" + defaultUnderline := "\x1b[0;4;39m" + brightBlackUnderline := "\x1b[0;4;90m" + dimBlackUnderline := "\x1b[0;2;4;37m" + + tests := []struct { + name string + cs *ColorScheme + input string + expected string + }{ + { + name: "when color is disabled, text is not stylized", + cs: NewColorScheme(false, false, false, NoTheme), + input: "this should not be stylized", + expected: "this should not be stylized", + }, + { + name: "when 4-bit color is enabled but no theme, 4-bit default color and underline are used", + cs: NewColorScheme(true, false, false, NoTheme), + input: "this should have no explicit color but underlined", + expected: fmt.Sprintf("%sthis should have no explicit color but underlined%s", defaultUnderline, reset), + }, + { + name: "when 4-bit color is enabled and theme is light, 4-bit dark color and underline are used", + cs: NewColorScheme(true, false, false, LightTheme), + input: "this should have dark foreground color and underlined", + expected: fmt.Sprintf("%sthis should have dark foreground color and underlined%s", brightBlackUnderline, reset), + }, + { + name: "when 4-bit color is enabled and theme is dark, 4-bit light color and underline are used", + cs: NewColorScheme(true, false, false, DarkTheme), + input: "this should have light foreground color and underlined", + expected: fmt.Sprintf("%sthis should have light foreground color and underlined%s", dimBlackUnderline, reset), + }, + { + name: "when 8-bit color is enabled but no theme, 4-bit default color and underline are used", + cs: NewColorScheme(true, true, false, NoTheme), + input: "this should have no explicit color but underlined", + expected: fmt.Sprintf("%sthis should have no explicit color but underlined%s", defaultUnderline, reset), + }, + { + name: "when 8-bit color is enabled and theme is light, 4-bit dark color and underline are used", + cs: NewColorScheme(true, true, false, LightTheme), + input: "this should have dark foreground color and underlined", + expected: fmt.Sprintf("%sthis should have dark foreground color and underlined%s", brightBlackUnderline, reset), + }, + { + name: "when 8-bit color is true and theme is dark, 4-bit light color and underline are used", + cs: NewColorScheme(true, true, false, DarkTheme), + input: "this should have light foreground color and underlined", + expected: fmt.Sprintf("%sthis should have light foreground color and underlined%s", dimBlackUnderline, reset), + }, + { + name: "when 24-bit color is enabled but no theme, 4-bit default color and underline are used", + cs: NewColorScheme(true, true, true, NoTheme), + input: "this should have no explicit color but underlined", + expected: fmt.Sprintf("%sthis should have no explicit color but underlined%s", defaultUnderline, reset), + }, + { + name: "when 24-bit color is enabled and theme is light, 4-bit dark color and underline are used", + cs: NewColorScheme(true, true, true, LightTheme), + input: "this should have dark foreground color and underlined", + expected: fmt.Sprintf("%sthis should have dark foreground color and underlined%s", brightBlackUnderline, reset), + }, + { + name: "when 24-bit color is true and theme is dark, 4-bit light color and underline are used", + cs: NewColorScheme(true, true, true, DarkTheme), + input: "this should have light foreground color and underlined", + expected: fmt.Sprintf("%sthis should have light foreground color and underlined%s", dimBlackUnderline, reset), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, tt.cs.TableHeader(tt.input)) + }) + } +} From efa1825eff655c0ae38ec2767940c20aa1137969 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:21:22 -0600 Subject: [PATCH 09/22] chore: update go-gh to v2.12.0 --- go.mod | 36 ++++++++++++++++++--------- go.sum | 78 ++++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 76 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index 433156789..bea712a2d 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,9 @@ require ( github.com/MakeNowJust/heredoc v1.0.0 github.com/briandowns/spinner v1.18.1 github.com/cenkalti/backoff/v4 v4.3.0 - github.com/charmbracelet/glamour v0.8.0 - github.com/charmbracelet/lipgloss v0.12.1 - github.com/cli/go-gh/v2 v2.11.2 + github.com/charmbracelet/glamour v0.9.2-0.20250319212134-549f544650e3 + github.com/charmbracelet/lipgloss v1.1.1-0.20250319133953-166f707985bc + github.com/cli/go-gh/v2 v2.12.0 github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24 github.com/cli/oauth v1.1.1 github.com/cli/safeexec v1.0.1 @@ -47,9 +47,9 @@ require ( github.com/stretchr/testify v1.10.0 github.com/zalando/go-keyring v0.2.5 golang.org/x/crypto v0.35.0 - golang.org/x/sync v0.11.0 - golang.org/x/term v0.29.0 - golang.org/x/text v0.22.0 + golang.org/x/sync v0.12.0 + golang.org/x/term v0.30.0 + golang.org/x/text v0.23.0 google.golang.org/grpc v1.69.4 google.golang.org/protobuf v1.36.5 gopkg.in/h2non/gock.v1 v1.1.2 @@ -57,13 +57,20 @@ require ( ) require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/alecthomas/chroma/v2 v2.14.0 // indirect github.com/alessio/shellescape v1.4.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/blang/semver v3.5.1+incompatible // indirect - github.com/charmbracelet/x/ansi v0.1.4 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/cli/browser v1.3.0 // indirect github.com/cli/shurcooL-graphql v0.0.4 // indirect github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect @@ -99,6 +106,7 @@ require ( github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/gojq v0.12.15 // indirect @@ -110,12 +118,14 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect @@ -130,6 +140,7 @@ require ( github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect github.com/sigstore/rekor v1.3.8 // indirect github.com/sigstore/sigstore v1.8.12 // indirect @@ -147,8 +158,9 @@ require ( github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect github.com/vbatts/tar-split v0.11.6 // indirect - github.com/yuin/goldmark v1.7.4 // indirect - github.com/yuin/goldmark-emoji v1.0.3 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yuin/goldmark v1.7.8 // indirect + github.com/yuin/goldmark-emoji v1.0.5 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.33.0 // indirect @@ -159,7 +171,7 @@ require ( golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.36.0 // indirect - golang.org/x/sys v0.30.0 // indirect + golang.org/x/sys v0.31.0 // indirect golang.org/x/tools v0.29.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect diff --git a/go.sum b/go.sum index b5db03d9c..2b5a31212 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ cloud.google.com/go/kms v1.20.4 h1:CJ0hMpOg1ANN9tx/a/GPJ+Uxudy8k6f3fvGFuTHiE5A= cloud.google.com/go/kms v1.20.4/go.mod h1:gPLsp1r4FblUgBYPOcvI/bUPpdMg2Jm1ZVKU4tQUfcc= cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdamKorcz/go-fuzz-headers-1 v0.0.0-20230919221257-8b5d3ce2d11d h1:zjqpY4C7H15HjRPEenkS4SAn3Jy2eRRjkjZbGR30TOg= @@ -33,6 +35,12 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.3.1 h1:gUDtaZk8het github.com/AzureAD/microsoft-authentication-library-for-go v1.3.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= @@ -91,19 +99,25 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= -github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw= -github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= -github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= -github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= -github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/exp/golden v0.0.0-20240715153702-9ba8adf781c4 h1:6KzMkQeAF56rggw2NZu1L+TH7j9+DM1/2Kmh7KUxg1I= -github.com/charmbracelet/x/exp/golden v0.0.0-20240715153702-9ba8adf781c4/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/glamour v0.9.2-0.20250319212134-549f544650e3 h1:hx6E25SvI2WiZdt/gxINcYBnHD7PE2Vr9auqwg5B05g= +github.com/charmbracelet/glamour v0.9.2-0.20250319212134-549f544650e3/go.mod h1:ihVqv4/YOY5Fweu1cxajuQrwJFh3zU4Ukb4mHVNjq3s= +github.com/charmbracelet/lipgloss v1.1.1-0.20250319133953-166f707985bc h1:nFRtCfZu/zkltd2lsLUPlVNv3ej/Atod9hcdbRZtlys= +github.com/charmbracelet/lipgloss v1.1.1-0.20250319133953-166f707985bc/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= +github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= -github.com/cli/go-gh/v2 v2.11.2 h1:oad1+sESTPNTiTvh3I3t8UmxuovNDxhwLzeMHk45Q9w= -github.com/cli/go-gh/v2 v2.11.2/go.mod h1:vVFhi3TfjseIW26ED9itAR8gQK0aVThTm8sYrsZ5QTI= +github.com/cli/go-gh/v2 v2.12.0 h1:PIurZ13fXbWDbr2//6ws4g4zDbryO+iDuTpiHgiV+6k= +github.com/cli/go-gh/v2 v2.12.0/go.mod h1:+5aXmEOJsH9fc9mBHfincDwnS02j2AIA/DsTH0Bk5uw= github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24 h1:QDrhR4JA2n3ij9YQN0u5ZeuvRIIvsUGmf5yPlTS0w8E= github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24/go.mod h1:rr9GNING0onuVw8MnracQHn7PcchnFlP882Y0II2KZk= github.com/cli/oauth v1.1.1 h1:459gD3hSjlKX9B1uXBuiAMdpXBUQ9QGf/NDcCpoQxPs= @@ -266,6 +280,8 @@ github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/in-toto/attestation v1.1.1 h1:QD3d+oATQ0dFsWoNh5oT0udQ3tUrOsZZ0Fc3tSgWbzI= github.com/in-toto/attestation v1.1.1/go.mod h1:Dcq1zVwA2V7Qin8I7rgOi+i837wEf/mOZwRm047Sjys= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= @@ -308,6 +324,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ= github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -325,8 +343,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -334,14 +352,18 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/microsoft/dev-tunnels v0.0.25 h1:UlMKUI+2O8cSu4RlB52ioSyn1LthYSVkJA+CSTsdKoA= github.com/microsoft/dev-tunnels v0.0.25/go.mod h1:frU++12T/oqxckXkDpTuYa427ncguEOodSPZcGCCrzQ= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= -github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38 h1:0FrBxrkJ0hVembTb/e4EU5Ml6vLcOusAqymmYISg5Uo= github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -401,6 +423,8 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc h1:vH0NQbIDk+mJLvBliNGfcQgUmhlniWBDXC79oRxfZA0= github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= @@ -466,12 +490,14 @@ github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs= github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= -github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= -github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4= -github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk= +github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= @@ -518,8 +544,8 @@ golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -529,19 +555,19 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From ffd42ea256a78dd852579f627365888dd5ad9b56 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:21:41 -0600 Subject: [PATCH 10/22] docs: add Sprig library functions to help topics --- pkg/cmd/root/help_topic.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/cmd/root/help_topic.go b/pkg/cmd/root/help_topic.go index 2b6432ef9..df5a2e0c6 100644 --- a/pkg/cmd/root/help_topic.go +++ b/pkg/cmd/root/help_topic.go @@ -151,6 +151,13 @@ var HelpTopics = []helpTopic{ - %[1]struncate %[1]s: ensures input fits within length - %[1]shyperlink %[1]s: renders a terminal hyperlink + The following Sprig template library functions can also be used with this formatting directive: + - %[1]scontains%[1]s: returns true if the input contains the argument + - %[1]shasPrefix%[1]s: returns true if the input has the argument as a prefix + - %[1]shasSuffix%[1]s: returns true if the input has the argument as a suffix + - %[1]sregexMatch%[1]s: returns true if the input matches the argument + For more information about the Sprig library, see . + To learn more about Go templates, see: . `, "`"), example: heredoc.Doc(` From 86ad62a62d6df9d5f048da15e10f81b6901bf36f Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Wed, 26 Mar 2025 13:42:39 -0400 Subject: [PATCH 11/22] Fixes #10590 Implement missing safeguard causing `gh alias delete` tests to wipe out tester's GitHub CLI configuration. --- pkg/cmd/alias/delete/delete_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/cmd/alias/delete/delete_test.go b/pkg/cmd/alias/delete/delete_test.go index 845119a80..9bc89830a 100644 --- a/pkg/cmd/alias/delete/delete_test.go +++ b/pkg/cmd/alias/delete/delete_test.go @@ -162,6 +162,9 @@ func TestDeleteRun(t *testing.T) { tt.opts.IO = ios cfg := config.NewFromString(tt.config) + cfg.WriteFunc = func() error { + return nil + } tt.opts.Config = func() (gh.Config, error) { return cfg, nil } From 229216e8308e51359f779f3798103dd3734870a4 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:29:20 -0600 Subject: [PATCH 12/22] doc(run shared): clarify 404 handling --- pkg/cmd/run/shared/shared.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cmd/run/shared/shared.go b/pkg/cmd/run/shared/shared.go index 248b5c5a0..ce909fd77 100644 --- a/pkg/cmd/run/shared/shared.go +++ b/pkg/cmd/run/shared/shared.go @@ -452,6 +452,8 @@ func preloadWorkflowNames(client *api.Client, repo ghrepo.Interface, runs []Run) // look it up directly without receiving a 404, but it is nonetheless // in the workflow run list. To handle this, we set the workflow name // to an empty string. + // Deciding to put this here instead of in GetWorkflow to allow + // the caller to decide what a 404 means. if httpErr, ok := err.(api.HTTPError); ok && httpErr.StatusCode == 404 { workflowMap[run.WorkflowID] = "" continue From 321e5687a6349fd6acae53c054e5e95c1eeda977 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Wed, 26 Mar 2025 17:40:35 -0400 Subject: [PATCH 13/22] Rewrote the gh at verify --help text to a) clarify and b) document the verificationResult object. --- pkg/cmd/attestation/verify/verify.go | 115 ++++++++++++++++++--------- 1 file changed, 79 insertions(+), 36 deletions(-) diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go index 65ae8ca3e..469b2f453 100644 --- a/pkg/cmd/attestation/verify/verify.go +++ b/pkg/cmd/attestation/verify/verify.go @@ -30,58 +30,101 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command Verify the integrity and provenance of an artifact using its associated cryptographically signed attestations. - In order to verify an attestation, you must validate the identity of the Actions - workflow that produced the attestation (a.k.a. the signer workflow). Given this - identity, the verification process checks the signatures in the attestations, - and confirms that the attestation refers to provided artifact. + ## Verification - To specify the artifact, the command requires: + In order to verify an attestation, you must provide an artifact and validate: + * the identity of the actor that produced the attestation + * the expected attestation predicate type + + By default, this command enforces the "%[2]s" + predicate type. To verify other attestation predicate types use the + %[1]s--predicate-type%[1]s flag. + + The "actor identity" consists of: + * the repository or the repository owner the artifact is linked with + * the Actions workflow that produced the attestation (a.k.a the + signer workflow) + + This identity is then validated against the attestation's certificate's + SourceRepository, SourceRepositoryOwner, and SubjectAlternativeName + (SAN) fields. + + It is up to you to decide how precisely you want to enforce this identity. + + At a minimum, this command requires either: + * the %[1]s--repo%[1]s flag (e.g. --repo github/example), or + * the %[1]s--owner%[1]s flag (e.g. --owner github) + + Ideally, the path of the signer workflow is also validated using the + %[1]s--signer-workflow%[1]s or %[1]s--cert-identity%[1]s flags. + + Please note: if your attestation was generated via a reusable workflow then + that reusable workflow is the signer whose identity needs to be validated. + In this situation, you must also use either the %[1]s--signer-workflow%[1]s or + the %[1]s--signer-repo%[1]s flag. + + For more options, see the other available flags. + + ## Loading Artifacts And Attestations + + To specify the artifact, this command requires: * a file path to an artifact, or * a container image URI (e.g. %[1]soci://%[1]s) * (note that if you provide an OCI URL, you must already be authenticated with its container registry) - To fetch the attestation, and validate the identity of the signer, the command - requires either: - * the %[1]s--repo%[1]s flag (e.g. --repo github/example). - * the %[1]s--owner%[1]s flag (e.g. --owner github), or + By default, this command will attempt to fetch relevant attestations via the + GitHub API using the values provided to %[1]s--owner%[1]s or %[1]s--repo%[1]s. - The %[1]s--repo%[1]s flag value must match the name of the GitHub repository - that the artifact is linked with. + To instead fetch attestations from your artifact's OCI registry, use the + %[1]s--bundle-from-oci%[1]s flag. - The %[1]s--owner%[1]s flag value must match the name of the GitHub organization - that the artifact's linked repository belongs to. + For offline verification using attestations stored on disk (c.f. the download command) + provide a path to the %[1]s--bundle%[1]s flag. - By default, the verify command will: - - only verify provenance attestations - - attempt to fetch relevant attestations via the GitHub API. + ## Additional Policy Enforcement - To verify other types of attestations, use the %[1]s--predicate-type%[1]s flag. + Given the %[1]s--format=json%[1]s flag, upon successful verification this + command will output a JSON array containing one entry per verified attestation. - To use your artifact's OCI registry instead of GitHub's API, use the - %[1]s--bundle-from-oci%[1]s flag. For offline verification, using attestations - stored on desk (c.f. the download command), provide a path to the %[1]s--bundle%[1]s flag. + This output can then be used for additional policy enforcement, i.e. by being + piped into a policy engine. - To see the full results that are generated upon successful verification, i.e. - for use with a policy engine, provide the %[1]s--format=json%[1]s flag. + Each object in the array contains two properties: + * an %[1]sattestation%[1]s object, which contains the bundle that was verified + * a %[1]sverificationResult%[1]s object, which is a parsed representation of the + contents of the bundle that was verified. - The signer workflow's identity is validated against the Subject Alternative Name (SAN) - within the attestation certificate. Often, the signer workflow is the - same workflow that started the run and generated the attestation, and will be - located inside your repository. For this reason, by default this command uses - either the %[1]s--repo%[1]s or the %[1]s--owner%[1]s flag value to validate the SAN. + Within the %[1]sverificationResult%[1]s object you will find: + * %[1]ssignature.certificate%[1]s, which is a parsed representation of the X.509 + certificate embedded in the attestation, + * %[1]sverifiedTimestamps%[1]s, an array of objects denoting when the attestation + was witnessed by a transparency log or a timestamp authority + * %[1]sstatement%[1]s, which contains the %[1]ssubject%[1]s array referencing artifacts, + the %[1]spredicateType%[1]s field, and the %[1]spredicate%[1]s object which contains + additional, often user-controllable, metadata - However, sometimes the caller workflow is not the same workflow that - performed the signing. If your attestation was generated via a reusable - workflow, then that reusable workflow is the signer whose identity needs to be - validated. In this situation, the signer workflow may or may not be located - inside your %[1]s--repo%[1]s or %[1]s--owner%[1]s. + IMPORTANT: please note that only the %[1]ssignature.certificate%[1]s and the + %[1]sverifiedTimestamps%[1]s properties contain values that cannot be + manipulated by the workflow that originated the attestation. - When using reusable workflows, use the %[1]s--signer-repo%[1]s, %[1]s--signer-workflow%[1]s, - or %[1]s--cert-identity%[1]s flags to validate the signer workflow's identity. + When dealing with attestations created within GitHub Actions, the contents of + %[1]ssignature.certificate%[1]s are populated directly from the OpenID Connect + token that GitHub has generated. The contents of the %[1]sverifiedTimestamps%[1]s + array are populated from the signed timestamps originating from either a + transparency log or a timestamp authority – and likewise cannot be forged by users. - For more policy verification options, see the other available flags. - `, "`"), + When designing policy enforcement using this output, special care must be taken + when examining the contents of the %[1]sstatement.predicate%[1]s property: + should an attacker gain access to your workflow's execution context, they + could then falsify the contents of the %[1]sstatement.predicate%[1]s. + + To mitigate this attack vector, consider using a "trusted builder": when generating + an artifact, have the build and attestation signing occur within a reusable workflow + whose execution cannot be influenced by input provided through the caller workflow. + + See above re: %[1]s--signer-workflow%[1]s. + `, "`", verification.SLSAPredicateV1), Example: heredoc.Doc(` # Verify an artifact linked with a repository $ gh attestation verify example.bin --repo github/example From 33ab0b8f3b3558f2b8134089664bbdc418173bba Mon Sep 17 00:00:00 2001 From: Phill MV Date: Thu, 27 Mar 2025 09:47:11 -0400 Subject: [PATCH 14/22] Tweaked language a bit, improved error message. --- pkg/cmd/attestation/verify/policy.go | 2 +- pkg/cmd/attestation/verify/verify.go | 40 ++++++++++++++++------------ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/pkg/cmd/attestation/verify/policy.go b/pkg/cmd/attestation/verify/policy.go index 1060a781e..1d1595eca 100644 --- a/pkg/cmd/attestation/verify/policy.go +++ b/pkg/cmd/attestation/verify/policy.go @@ -161,7 +161,7 @@ func validateSignerWorkflow(hostname, signerWorkflow string) (string, error) { // if the provided workflow did not match the expect format // we move onto creating a signer workflow using the provided host name if hostname == "" { - return "", errors.New("unknown host") + return "", errors.New("unknown signer workflow host") } return fmt.Sprintf("^https://%s/%s", hostname, signerWorkflow), nil diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go index 469b2f453..0acea06c7 100644 --- a/pkg/cmd/attestation/verify/verify.go +++ b/pkg/cmd/attestation/verify/verify.go @@ -30,13 +30,16 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command Verify the integrity and provenance of an artifact using its associated cryptographically signed attestations. - ## Verification + ## Understanding Verification + + An attestation is a claim (i.e. a provenance statement) made by an actor + (i.e. a GitHub Actions workflow) regarding a subject (i.e. an artifact). In order to verify an attestation, you must provide an artifact and validate: * the identity of the actor that produced the attestation - * the expected attestation predicate type + * the expected attestation predicate type (the nature of the claim) - By default, this command enforces the "%[2]s" + By default, this command enforces the %[1]s%[2]s%[1]s predicate type. To verify other attestation predicate types use the %[1]s--predicate-type%[1]s flag. @@ -52,8 +55,11 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command It is up to you to decide how precisely you want to enforce this identity. At a minimum, this command requires either: - * the %[1]s--repo%[1]s flag (e.g. --repo github/example), or - * the %[1]s--owner%[1]s flag (e.g. --owner github) + * the %[1]s--owner%[1]s flag (e.g. --owner github), or + * the %[1]s--repo%[1]s flag (e.g. --repo github/example) + + The more precisely you specify the identity, the more control you will + have over the security guarantees offered by the verification process. Ideally, the path of the signer workflow is also validated using the %[1]s--signer-workflow%[1]s or %[1]s--cert-identity%[1]s flags. @@ -224,23 +230,23 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command verifyCmd.Flags().StringVarP(&opts.Repo, "repo", "R", "", "Repository name in the format /") verifyCmd.MarkFlagsMutuallyExclusive("owner", "repo") verifyCmd.MarkFlagsOneRequired("owner", "repo") - verifyCmd.Flags().StringVarP(&opts.PredicateType, "predicate-type", "", verification.SLSAPredicateV1, "Filter attestations by provided predicate type") verifyCmd.Flags().BoolVarP(&opts.NoPublicGood, "no-public-good", "", false, "Do not verify attestations signed with Sigstore public good instance") verifyCmd.Flags().StringVarP(&opts.TrustedRoot, "custom-trusted-root", "", "", "Path to a trusted_root.jsonl file; likely for offline verification") verifyCmd.Flags().IntVarP(&opts.Limit, "limit", "L", api.DefaultLimit, "Maximum number of attestations to fetch") cmdutil.AddFormatFlags(verifyCmd, &opts.exporter) - // policy enforcement flags - verifyCmd.Flags().BoolVarP(&opts.DenySelfHostedRunner, "deny-self-hosted-runners", "", false, "Fail verification for attestations generated on self-hosted runners") - verifyCmd.Flags().StringVarP(&opts.SAN, "cert-identity", "", "", "Enforce that the certificate's subject alternative name matches the provided value exactly") - verifyCmd.Flags().StringVarP(&opts.SANRegex, "cert-identity-regex", "i", "", "Enforce that the certificate's subject alternative name matches the provided regex") - verifyCmd.Flags().StringVarP(&opts.SignerRepo, "signer-repo", "", "", "Repository of reusable workflow that signed attestation in the format /") - verifyCmd.Flags().StringVarP(&opts.SignerWorkflow, "signer-workflow", "", "", "Workflow that signed attestation in the format [host/]////") - verifyCmd.MarkFlagsMutuallyExclusive("cert-identity", "cert-identity-regex", "signer-repo", "signer-workflow") - verifyCmd.Flags().StringVarP(&opts.OIDCIssuer, "cert-oidc-issuer", "", verification.GitHubOIDCIssuer, "Issuer of the OIDC token") verifyCmd.Flags().StringVarP(&opts.Hostname, "hostname", "", "", "Configure host to use") - verifyCmd.Flags().StringVarP(&opts.SignerDigest, "signer-digest", "", "", "Digest associated with the signer workflow") - verifyCmd.Flags().StringVarP(&opts.SourceRef, "source-ref", "", "", "Ref associated with the source workflow") - verifyCmd.Flags().StringVarP(&opts.SourceDigest, "source-digest", "", "", "Digest associated with the source workflow") + // policy enforcement flags + verifyCmd.Flags().StringVarP(&opts.PredicateType, "predicate-type", "", verification.SLSAPredicateV1, "Enforce that verified attestations' predicate type matches the provided value") + verifyCmd.Flags().BoolVarP(&opts.DenySelfHostedRunner, "deny-self-hosted-runners", "", false, "Fail verification for attestations generated on self-hosted runners") + verifyCmd.Flags().StringVarP(&opts.SAN, "cert-identity", "", "", "Enforce that the certificate's SubjectAlternativeName matches the provided value exactly") + verifyCmd.Flags().StringVarP(&opts.SANRegex, "cert-identity-regex", "i", "", "Enforce that the certificate's SubjectAlternativeName matches the provided regex") + verifyCmd.Flags().StringVarP(&opts.SignerRepo, "signer-repo", "", "", "Enforce that the workflow that signed the attestation's repository matches the provided value (/)") + verifyCmd.Flags().StringVarP(&opts.SignerWorkflow, "signer-workflow", "", "", "Enforce that the workflow that signed the attestation matches the provided value ([host/]////)") + verifyCmd.MarkFlagsMutuallyExclusive("cert-identity", "cert-identity-regex", "signer-repo", "signer-workflow") + verifyCmd.Flags().StringVarP(&opts.OIDCIssuer, "cert-oidc-issuer", "", verification.GitHubOIDCIssuer, "Enforce that the issuer of the OIDC token matches the provided value") + verifyCmd.Flags().StringVarP(&opts.SignerDigest, "signer-digest", "", "", "Enforce that the digest associated with the signer workflow matches the provided value") + verifyCmd.Flags().StringVarP(&opts.SourceRef, "source-ref", "", "", "Enforce that the git ref associated with the source repository matches the provided value") + verifyCmd.Flags().StringVarP(&opts.SourceDigest, "source-digest", "", "", "Enforce that the digest associated with the source repository matches the provided value") return verifyCmd } From 9c9b158d12f0ae735391373c63d102f2322b3f64 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Thu, 27 Mar 2025 09:55:14 -0400 Subject: [PATCH 15/22] added minor caveat --- pkg/cmd/attestation/verify/verify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go index 0acea06c7..34e538612 100644 --- a/pkg/cmd/attestation/verify/verify.go +++ b/pkg/cmd/attestation/verify/verify.go @@ -50,7 +50,7 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command This identity is then validated against the attestation's certificate's SourceRepository, SourceRepositoryOwner, and SubjectAlternativeName - (SAN) fields. + (SAN) fields, among others. It is up to you to decide how precisely you want to enforce this identity. From f099a542438bd12b7cdca56b39150bc7226d2438 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Thu, 27 Mar 2025 09:57:00 -0400 Subject: [PATCH 16/22] updated test --- pkg/cmd/attestation/verify/policy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/attestation/verify/policy_test.go b/pkg/cmd/attestation/verify/policy_test.go index d376498b6..ff10cad11 100644 --- a/pkg/cmd/attestation/verify/policy_test.go +++ b/pkg/cmd/attestation/verify/policy_test.go @@ -275,7 +275,7 @@ func TestValidateSignerWorkflow(t *testing.T) { name: "workflow with no host specified", providedSignerWorkflow: "github/artifact-attestations-workflows/.github/workflows/attest.yml", expectErr: true, - errContains: "unknown host", + errContains: "unknown signer workflow host", }, { name: "workflow with default host", From c1fbc2f05bf301f6b49b07eb3fef4826357bb108 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Thu, 27 Mar 2025 14:21:13 -0600 Subject: [PATCH 17/22] test(many): fix whitespace in tests expectations Due to https://github.com/charmbracelet/glamour/pull/334, the margin used for markdown rendering has changed by a couple spaces. This corrects the relevant tests to accomodate that change. --- pkg/cmd/gist/view/view_test.go | 4 ++-- pkg/cmd/pr/create/create_test.go | 4 ++-- pkg/cmd/pr/review/review_test.go | 2 +- pkg/cmd/release/view/view_test.go | 29 ++++++++++++++--------------- pkg/cmd/repo/view/view_test.go | 10 +++++----- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/pkg/cmd/gist/view/view_test.go b/pkg/cmd/gist/view/view_test.go index 704766e10..706b85f10 100644 --- a/pkg/cmd/gist/view/view_test.go +++ b/pkg/cmd/gist/view/view_test.go @@ -234,7 +234,7 @@ func Test_viewRun(t *testing.T) { }, }, }, - wantOut: "cicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n\n # foo \n\n", + wantOut: "cicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n\n # foo \n\n", }, { name: "multiple files, trailing newlines", @@ -277,7 +277,7 @@ func Test_viewRun(t *testing.T) { }, }, }, - wantOut: "some files\n\ncicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n\n \n • foo \n\n", + wantOut: "some files\n\ncicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n\n \n • foo \n\n", }, { name: "multiple files, raw", diff --git a/pkg/cmd/pr/create/create_test.go b/pkg/cmd/pr/create/create_test.go index 4d7c294db..55012d7dd 100644 --- a/pkg/cmd/pr/create/create_test.go +++ b/pkg/cmd/pr/create/create_test.go @@ -496,7 +496,7 @@ func Test_createRun(t *testing.T) { `MaintainerCanModify: false`, `Body:`, ``, - ` my body `, + ` my body `, ``, ``, }, @@ -568,7 +568,7 @@ func Test_createRun(t *testing.T) { `MaintainerCanModify: false`, `Body:`, ``, - ` BODY `, + ` BODY `, ``, ``, }, diff --git a/pkg/cmd/pr/review/review_test.go b/pkg/cmd/pr/review/review_test.go index 611f71894..f9e00c3b8 100644 --- a/pkg/cmd/pr/review/review_test.go +++ b/pkg/cmd/pr/review/review_test.go @@ -283,7 +283,7 @@ func TestPRReview_interactive(t *testing.T) { assert.Equal(t, heredoc.Doc(` Got: - cool story + cool story `), output.String()) assert.Equal(t, "✓ Approved pull request OWNER/REPO#123\n", output.Stderr()) diff --git a/pkg/cmd/release/view/view_test.go b/pkg/cmd/release/view/view_test.go index 1fed0fdc8..a5dd3b2e2 100644 --- a/pkg/cmd/release/view/view_test.go +++ b/pkg/cmd/release/view/view_test.go @@ -141,19 +141,18 @@ func Test_viewRun(t *testing.T) { opts: ViewOptions{ TagName: "v1.2.3", }, - wantStdout: heredoc.Doc(` - v1.2.3 - MonaLisa released this about 1 day ago - - - • Fixed bugs - - - Assets - windows.zip 12 B - linux.tgz 34 B - - View on GitHub: https://github.com/OWNER/REPO/releases/tags/v1.2.3 + wantStdout: heredoc.Doc(`v1.2.3 + MonaLisa released this about 1 day ago + + + • Fixed bugs + + + Assets + windows.zip 12 B + linux.tgz 34 B + + View on GitHub: https://github.com/OWNER/REPO/releases/tags/v1.2.3 `), wantStderr: ``, }, @@ -169,8 +168,8 @@ func Test_viewRun(t *testing.T) { v1.2.3 MonaLisa released this about 1 day ago - - • Fixed bugs + + • Fixed bugs Assets diff --git a/pkg/cmd/repo/view/view_test.go b/pkg/cmd/repo/view/view_test.go index d3b6d619b..f07f9de18 100644 --- a/pkg/cmd/repo/view/view_test.go +++ b/pkg/cmd/repo/view/view_test.go @@ -260,7 +260,7 @@ func Test_ViewRun(t *testing.T) { social distancing - # truly cool readme check it out + # truly cool readme check it out @@ -279,7 +279,7 @@ func Test_ViewRun(t *testing.T) { social distancing - # truly cool readme check it out + # truly cool readme check it out @@ -297,7 +297,7 @@ func Test_ViewRun(t *testing.T) { social distancing - # truly cool readme check it out + # truly cool readme check it out @@ -312,7 +312,7 @@ func Test_ViewRun(t *testing.T) { social distancing - # truly cool readme check it out + # truly cool readme check it out @@ -650,7 +650,7 @@ func Test_ViewRun_HandlesSpecialCharacters(t *testing.T) { Some basic special characters " & / < > ' - # < is always > than & ' and " + # < is always > than & ' and " From 89def92f042505614f72d2fba8142f6808e6541d Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 28 Mar 2025 09:51:32 -0600 Subject: [PATCH 18/22] doc(formatting): update sprig func descriptions --- pkg/cmd/root/help_topic.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/root/help_topic.go b/pkg/cmd/root/help_topic.go index df5a2e0c6..5e68f99d9 100644 --- a/pkg/cmd/root/help_topic.go +++ b/pkg/cmd/root/help_topic.go @@ -152,10 +152,10 @@ var HelpTopics = []helpTopic{ - %[1]shyperlink %[1]s: renders a terminal hyperlink The following Sprig template library functions can also be used with this formatting directive: - - %[1]scontains%[1]s: returns true if the input contains the argument - - %[1]shasPrefix%[1]s: returns true if the input has the argument as a prefix - - %[1]shasSuffix%[1]s: returns true if the input has the argument as a suffix - - %[1]sregexMatch%[1]s: returns true if the input matches the argument + - %[1]scontains %[1]s: checks if %[1]sstring%[1]s contains %[1]sarg%[1]s + - %[1]shasPrefix %[1]s: checks if %[1]sstring%[1]s starts with %[1]sprefix%[1]s + - %[1]shasSuffix %[1]s: checks if %[1]sstring%[1]s ends with %[1]ssuffix%[1]s + - %[1]sregexMatch %[1]s: checks if %[1]sstring%[1]s has any matches for %[1]sregex%[1]s For more information about the Sprig library, see . To learn more about Go templates, see: . From eabd02793e5cbaab5307049a4a4dc7e7a96e5ce0 Mon Sep 17 00:00:00 2001 From: Andy Feller Date: Fri, 28 Mar 2025 13:19:27 -0400 Subject: [PATCH 19/22] Renaming tabl eheader variables for maintainability --- pkg/iostreams/color.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/pkg/iostreams/color.go b/pkg/iostreams/color.go index e2841d030..07fdcd79f 100644 --- a/pkg/iostreams/color.go +++ b/pkg/iostreams/color.go @@ -15,24 +15,24 @@ const ( highlightStyle = "black:yellow" ) -// Special cases like darkTableHeader / lightTableHeader are necessary when using color and modifiers +// Special cases like darkThemeTableHeader / lightThemeTableHeader are necessary when using color and modifiers // (bold, underline, dim) because ansi.ColorFunc requires a foreground color and resets formats. var ( - magenta = ansi.ColorFunc("magenta") - cyan = ansi.ColorFunc("cyan") - red = ansi.ColorFunc("red") - yellow = ansi.ColorFunc("yellow") - blue = ansi.ColorFunc("blue") - green = ansi.ColorFunc("green") - gray = ansi.ColorFunc("black+h") - bold = ansi.ColorFunc("default+b") - cyanBold = ansi.ColorFunc("cyan+b") - greenBold = ansi.ColorFunc("green+b") - highlightStart = ansi.ColorCode(highlightStyle) - highlight = ansi.ColorFunc(highlightStyle) - darkTableHeader = ansi.ColorFunc("white+du") - lightTableHeader = ansi.ColorFunc("black+hu") - noneTableHeader = ansi.ColorFunc("default+u") + magenta = ansi.ColorFunc("magenta") + cyan = ansi.ColorFunc("cyan") + red = ansi.ColorFunc("red") + yellow = ansi.ColorFunc("yellow") + blue = ansi.ColorFunc("blue") + green = ansi.ColorFunc("green") + gray = ansi.ColorFunc("black+h") + bold = ansi.ColorFunc("default+b") + cyanBold = ansi.ColorFunc("cyan+b") + greenBold = ansi.ColorFunc("green+b") + highlightStart = ansi.ColorCode(highlightStyle) + highlight = ansi.ColorFunc(highlightStyle) + darkThemeTableHeader = ansi.ColorFunc("white+du") + lightThemeTableHeader = ansi.ColorFunc("black+hu") + noThemeTableHeader = ansi.ColorFunc("default+u") gray256 = func(t string) string { return fmt.Sprintf("\x1b[%d;5;%dm%s\x1b[m", 38, 242, t) @@ -268,10 +268,10 @@ func (c *ColorScheme) TableHeader(t string) string { switch c.theme { case DarkTheme: - return darkTableHeader(t) + return darkThemeTableHeader(t) case LightTheme: - return lightTableHeader(t) + return lightThemeTableHeader(t) default: - return noneTableHeader(t) + return noThemeTableHeader(t) } } From c6b4da8f2072d56d40a98c2046369d110988638d Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:00:06 -0600 Subject: [PATCH 20/22] doc(formatting): add line breaks for readability --- pkg/cmd/root/help_topic.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cmd/root/help_topic.go b/pkg/cmd/root/help_topic.go index 5e68f99d9..db0ef098d 100644 --- a/pkg/cmd/root/help_topic.go +++ b/pkg/cmd/root/help_topic.go @@ -138,6 +138,7 @@ var HelpTopics = []helpTopic{ The %[1]s--template%[1]s flag requires a string argument in Go template syntax, and will only print those JSON values which match the query. + In addition to the Go template functions in the standard library, the following functions can be used with this formatting directive: - %[1]sautocolor%[1]s: like %[1]scolor%[1]s, but only emits color to terminals @@ -156,6 +157,7 @@ var HelpTopics = []helpTopic{ - %[1]shasPrefix %[1]s: checks if %[1]sstring%[1]s starts with %[1]sprefix%[1]s - %[1]shasSuffix %[1]s: checks if %[1]sstring%[1]s ends with %[1]ssuffix%[1]s - %[1]sregexMatch %[1]s: checks if %[1]sstring%[1]s has any matches for %[1]sregex%[1]s + For more information about the Sprig library, see . To learn more about Go templates, see: . From aac43870d4526ebc88ee0ec5151f0bf857483e19 Mon Sep 17 00:00:00 2001 From: Kynan Ware <47394200+BagToad@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:11:57 -0600 Subject: [PATCH 21/22] test(release view): fix indentation --- pkg/cmd/release/view/view_test.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/pkg/cmd/release/view/view_test.go b/pkg/cmd/release/view/view_test.go index a5dd3b2e2..8ca4f14c8 100644 --- a/pkg/cmd/release/view/view_test.go +++ b/pkg/cmd/release/view/view_test.go @@ -141,18 +141,19 @@ func Test_viewRun(t *testing.T) { opts: ViewOptions{ TagName: "v1.2.3", }, - wantStdout: heredoc.Doc(`v1.2.3 - MonaLisa released this about 1 day ago - - - • Fixed bugs - - - Assets - windows.zip 12 B - linux.tgz 34 B - - View on GitHub: https://github.com/OWNER/REPO/releases/tags/v1.2.3 + wantStdout: heredoc.Doc(` + v1.2.3 + MonaLisa released this about 1 day ago + + + • Fixed bugs + + + Assets + windows.zip 12 B + linux.tgz 34 B + + View on GitHub: https://github.com/OWNER/REPO/releases/tags/v1.2.3 `), wantStderr: ``, }, From 0427f2688440ef515078a03d81ee64a6d4d9fdd2 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Mon, 31 Mar 2025 11:05:23 -0400 Subject: [PATCH 22/22] Update pkg/cmd/attestation/verify/verify.go Co-authored-by: Meredith Lancaster --- pkg/cmd/attestation/verify/verify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go index 34e538612..3affdfabb 100644 --- a/pkg/cmd/attestation/verify/verify.go +++ b/pkg/cmd/attestation/verify/verify.go @@ -66,7 +66,7 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command Please note: if your attestation was generated via a reusable workflow then that reusable workflow is the signer whose identity needs to be validated. - In this situation, you must also use either the %[1]s--signer-workflow%[1]s or + In this situation, you must use either the %[1]s--signer-workflow%[1]s or the %[1]s--signer-repo%[1]s flag. For more options, see the other available flags.