Skip to content

Commit 2b510c8

Browse files
authored
Add recommended actions to sourcetool status (#224)
* Tool: Add FindWorkflowPR func Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Regenerate fakes Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Display existing workflow prs Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add FindPolicyPR function Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add CheckPolicyRepoFork func Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add status suggestions Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add pr/forks integration tests Signed-off-by: Adolfo Garcia Veytia (puerco) <[email protected]> --------- Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> Signed-off-by: Adolfo Garcia Veytia (puerco) <[email protected]>
1 parent 9840ba5 commit 2b510c8

File tree

5 files changed

+453
-2
lines changed

5 files changed

+453
-2
lines changed

sourcetool/internal/cmd/status.go

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ sourcetool status myorg/myrepo@mybranch
9696

9797
cmd.SilenceUsage = true
9898

99+
actions := []recommendedAction{}
100+
99101
ctx := context.Background()
100102
ghc := ghcontrol.NewGhConnection(opts.owner, opts.repository, opts.branch)
101103

@@ -110,11 +112,20 @@ sourcetool status myorg/myrepo@mybranch
110112
return err
111113
}
112114

115+
// Get the active repository controls
113116
controls, err := srctool.GetRepoControls()
114117
if err != nil {
115118
return fmt.Errorf("fetching active controls: %w", err)
116119
}
117120

121+
// Check if the user has a fork of the policy repo:
122+
policyForkFound, err := srctool.CheckPolicyRepoFork()
123+
if err != nil {
124+
return fmt.Errorf("checking for a fork of the policy repo: %w", err)
125+
}
126+
127+
// Check if the user has a fork of the repository we want to protect
128+
118129
// Check if there is a policy:
119130
pcy, _, err := policy.NewPolicyEvaluator().GetPolicy(ctx, ghc)
120131
if err != nil {
@@ -125,7 +136,7 @@ sourcetool status myorg/myrepo@mybranch
125136
toplevel := policy.ComputeEligibleSlsaLevel(controls)
126137

127138
title := fmt.Sprintf(
128-
"SLSA Source Status for %s/%s@%s", opts.owner, opts.repository,
139+
"\nSLSA Source Status for %s/%s@%s", opts.owner, opts.repository,
129140
ghcontrol.BranchToFullRef(opts.branch),
130141
)
131142
fmt.Printf("")
@@ -137,14 +148,63 @@ sourcetool status myorg/myrepo@mybranch
137148
if slices.Contains(controls.Names(), c) {
138149
fmt.Println("✅")
139150
} else {
151+
//nolint:exhaustive // We don't display all labels here
152+
switch c {
153+
case slsa.ProvenanceAvailable:
154+
prdata, err := srctool.FindWorkflowPR()
155+
if err != nil {
156+
return err
157+
}
158+
159+
if prdata != nil {
160+
fmt.Printf("⏳ (PR %s/%s#%d waiting to merge)\n", prdata.Owner, prdata.Repo, prdata.Number)
161+
actions = append(actions, recommendedAction{
162+
Text: "Merge provenance workflow pull request",
163+
})
164+
continue
165+
}
166+
167+
actions = append(actions, recommendedAction{
168+
Text: fmt.Sprintf("Start generating provenance on %s/%s", opts.owner, opts.repository),
169+
Command: fmt.Sprintf("sourcetool setup controls --config=CONFIG_PROVENANCE_WORKFLOW %s/%s", opts.owner, opts.repository),
170+
})
171+
case slsa.ContinuityEnforced:
172+
actions = append(actions, recommendedAction{
173+
Text: "Enable branch push/delete protection",
174+
Command: fmt.Sprintf("sourcetool setup controls --config=CONFIG_BRANCH_RULES %s/%s", opts.owner, opts.repository),
175+
})
176+
}
140177
fmt.Println("🚫")
141178
}
142179
}
143180

144181
fmt.Println("")
145182
fmt.Printf("%-35s ", "Repo policy found:")
146183
if pcy == nil {
147-
fmt.Println("🚫")
184+
prdata, err := srctool.FindPolicyPR()
185+
if err != nil {
186+
return fmt.Errorf("looking for policy PR: %w", err)
187+
}
188+
189+
if prdata != nil {
190+
fmt.Printf("⏳ (PR %s/%s#%d waiting to merge)\n", prdata.Owner, prdata.Repo, prdata.Number)
191+
actions = append(actions, recommendedAction{
192+
Text: "Wait for policy pull request to merge",
193+
})
194+
} else {
195+
if policyForkFound {
196+
actions = append(actions, recommendedAction{
197+
Text: fmt.Sprintf("Create and commit a source policy for %s/%s", opts.owner, opts.repository),
198+
Command: fmt.Sprintf("sourcetool setup controls --config=CONFIG_POLICY %s/%s", opts.owner, opts.repository),
199+
})
200+
} else {
201+
actions = append(actions, recommendedAction{
202+
Text: fmt.Sprintf("Create a fork of the SLSA policies repo (%s)", srctool.Options.PolicyRepo),
203+
Command: fmt.Sprintf("Open https://github.com/%s/fork", srctool.Options.PolicyRepo),
204+
})
205+
}
206+
fmt.Println("🚫")
207+
}
148208
} else {
149209
fmt.Println("✅")
150210
}
@@ -153,9 +213,24 @@ sourcetool status myorg/myrepo@mybranch
153213
fmt.Println(w("Current SLSA Source level: " + toplevel))
154214
fmt.Println("")
155215

216+
fmt.Println("Recommended actions:")
217+
218+
for _, a := range actions {
219+
fmt.Printf(" - %s\n", a.Text)
220+
if a.Command != "" {
221+
fmt.Printf(" > %s\n", a.Command)
222+
}
223+
fmt.Println()
224+
}
225+
156226
return nil
157227
},
158228
}
159229
opts.AddFlags(statusCmd)
160230
parentCmd.AddCommand(statusCmd)
161231
}
232+
233+
type recommendedAction struct {
234+
Text string
235+
Command string
236+
}

sourcetool/pkg/sourcetool/implementation.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/carabiner-dev/github"
1818
gogit "github.com/go-git/go-git/v5"
19+
gogithub "github.com/google/go-github/v69/github"
1920
"github.com/sirupsen/logrus"
2021
kgithub "sigs.k8s.io/release-sdk/github"
2122

@@ -74,6 +75,7 @@ type toolImplementation interface {
7475
CheckPolicyFork(*options.Options) error
7576
CreatePolicyPR(*options.Options) error
7677
CheckForks(*options.Options) error
78+
SearchPullRequest(*options.Options, string) (int, error)
7779
}
7880

7981
type defaultToolImplementation struct{}
@@ -247,6 +249,10 @@ func (impl *defaultToolImplementation) CheckWorkflowFork(opts *options.Options)
247249
userForkOrg := opts.UserForkOrg
248250
userForkRepo := opts.Repo // For now we only support forks with the same name
249251

252+
if userForkOrg == "" {
253+
return errors.New("unable to check for for, user org not set")
254+
}
255+
250256
if err := kgithub.VerifyFork(
251257
fmt.Sprintf("slsa-source-workflow-%d", time.Now().Unix()), userForkOrg, userForkRepo, opts.Owner, opts.Repo,
252258
); err != nil {
@@ -336,9 +342,20 @@ func (impl *defaultToolImplementation) CheckPolicyFork(opts *options.Options) er
336342
if !ok || policyRepo == "" {
337343
return fmt.Errorf("unable to parse policy repository slug")
338344
}
345+
346+
if opts.UserForkOrg == "" {
347+
if err := getUserData(opts); err != nil {
348+
return err
349+
}
350+
}
351+
339352
userForkOrg := opts.UserForkOrg
340353
userForkRepo := policyRepo // For now we only support forks with the same name
341354

355+
if userForkOrg == "" {
356+
return errors.New("unable to check for for, user org not set")
357+
}
358+
342359
// Check the user has a fork of the slsa repo
343360
if err := kgithub.VerifyFork(
344361
fmt.Sprintf("slsa-source-policy-%d", time.Now().Unix()), userForkOrg, userForkRepo, policyOrg, policyRepo,
@@ -439,3 +456,28 @@ func (impl *defaultToolImplementation) CheckForks(opts *options.Options) error {
439456
}
440457
return errors.Join(errs...)
441458
}
459+
460+
// SearchPullRequest searches the last pull requests on a repo for one whose
461+
// title matches the query string
462+
func (impl *defaultToolImplementation) SearchPullRequest(opts *options.Options, query string) (int, error) {
463+
gcx, err := opts.GetGitHubConnection()
464+
if err != nil {
465+
return 0, err
466+
}
467+
468+
prs, _, err := gcx.Client().PullRequests.List(
469+
context.Background(), opts.Owner, opts.Repo, &gogithub.PullRequestListOptions{
470+
State: "open",
471+
},
472+
)
473+
if err != nil {
474+
return 0, fmt.Errorf("listing pull requests: %w", err)
475+
}
476+
477+
for _, pr := range prs {
478+
if strings.Contains(pr.GetTitle(), query) {
479+
return pr.GetNumber(), nil
480+
}
481+
}
482+
return 0, nil
483+
}

sourcetool/pkg/sourcetool/sourcetoolfakes/fake_tool_implementation.go

Lines changed: 81 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)