Skip to content

Commit 1f8654f

Browse files
authored
Policy subcommand (#234)
* Add option and fn to open policy PRs Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add sourcetool CreateRepositoryPolicy func Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add policy subcommand Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> --------- Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]>
1 parent 5d89aa4 commit 1f8654f

File tree

5 files changed

+266
-3
lines changed

5 files changed

+266
-3
lines changed

sourcetool/internal/cmd/policy.go

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
9+
"github.com/spf13/cobra"
10+
11+
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/policy"
12+
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/sourcetool"
13+
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/sourcetool/models"
14+
)
15+
16+
type policyViewOpts struct {
17+
repoOptions
18+
}
19+
20+
type policyCreateOpts struct {
21+
branchOptions
22+
openPullRequest bool
23+
}
24+
25+
func (pco *policyCreateOpts) AddFlags(cmd *cobra.Command) {
26+
pco.branchOptions.AddFlags(cmd)
27+
cmd.PersistentFlags().BoolVar(&pco.openPullRequest, "pr", true, "Open a pull request to check-in the policy")
28+
}
29+
30+
func addPolicy(parentCmd *cobra.Command) {
31+
policyCmd := &cobra.Command{
32+
Short: "tools to work with source policies",
33+
Long: fmt.Sprintf(`
34+
%s %s
35+
36+
The policy subcommands can be used to view, create and update the source
37+
policy for a repository. The policy family has two subcommands:
38+
39+
%s
40+
Shows current repository policy for a repository checjed into the SLSA community
41+
policy repo.
42+
43+
%s
44+
Creates a new policy for a repository and, optionally, check it into the
45+
SLSA community repository.
46+
47+
`, w("sourcetool policy:"), w2("configure SLSA source policies for a repo"),
48+
w("sourcetool policy view"), w("sourcetool policy create")),
49+
Use: "policy",
50+
SilenceUsage: true,
51+
SilenceErrors: true,
52+
}
53+
54+
addPolicyView(policyCmd)
55+
addPolicyCreate(policyCmd)
56+
parentCmd.AddCommand(policyCmd)
57+
}
58+
59+
func addPolicyView(parent *cobra.Command) {
60+
opts := &policyViewOpts{}
61+
policyViewCmd := &cobra.Command{
62+
Short: "view the policy of a repository",
63+
Long: `The view subcommand retrieves the policy stored in the SLSA community
64+
repository for a repository and displays it.
65+
`,
66+
Use: "view owner/repo",
67+
SilenceUsage: false,
68+
SilenceErrors: true,
69+
PreRunE: func(_ *cobra.Command, args []string) error {
70+
if len(args) > 0 {
71+
if err := opts.ParseSlug(args[0]); err != nil {
72+
return err
73+
}
74+
}
75+
76+
// Validate early the repository options to provide a more
77+
// useful message to the user
78+
if err := opts.Validate(); err != nil {
79+
return err
80+
}
81+
82+
return nil
83+
},
84+
RunE: func(cmd *cobra.Command, args []string) (err error) {
85+
if err := opts.Validate(); err != nil {
86+
return err
87+
}
88+
89+
// At this point options are valid, no help needed.
90+
cmd.SilenceUsage = true
91+
92+
authenticator, err := CheckAuth()
93+
if err != nil {
94+
return err
95+
}
96+
97+
// Create a new sourcetool object
98+
srctool, err := sourcetool.New(
99+
sourcetool.WithAuthenticator(authenticator),
100+
// sourcetool.WithPolicyRepo(opts.policyRepo),
101+
)
102+
if err != nil {
103+
return err
104+
}
105+
106+
pcy, err := srctool.GetRepositoryPolicy(context.Background(), opts.GetRepository())
107+
if err != nil {
108+
return err
109+
}
110+
111+
if err := displayPolicy(opts.repoOptions, pcy); err != nil {
112+
return err
113+
}
114+
115+
return nil
116+
},
117+
}
118+
opts.AddFlags(policyViewCmd)
119+
parent.AddCommand(policyViewCmd)
120+
}
121+
122+
// addPolicyCreate adds the create subcreate
123+
func addPolicyCreate(parent *cobra.Command) {
124+
opts := &policyCreateOpts{}
125+
policyViewCmd := &cobra.Command{
126+
Short: "creates a source policy for a repository",
127+
Long: `The create subcommand inspects the controls in place for a repo
128+
and creates a new policy for it.
129+
`,
130+
Use: "create owner/repo@branch",
131+
SilenceUsage: false,
132+
SilenceErrors: true,
133+
PreRunE: func(_ *cobra.Command, args []string) error {
134+
if len(args) > 0 {
135+
if err := opts.ParseLocator(args[0]); err != nil {
136+
return err
137+
}
138+
}
139+
140+
// Validate early the repository options to provide a more
141+
// useful message to the user
142+
if err := opts.repoOptions.Validate(); err != nil {
143+
return err
144+
}
145+
146+
return nil
147+
},
148+
RunE: func(cmd *cobra.Command, args []string) (err error) {
149+
if err := opts.Validate(); err != nil {
150+
return err
151+
}
152+
153+
// At this point options are valid, no help needed.
154+
cmd.SilenceUsage = true
155+
156+
authenticator, err := CheckAuth()
157+
if err != nil {
158+
return err
159+
}
160+
161+
// Create a new sourcetool object
162+
srctool, err := sourcetool.New(
163+
sourcetool.WithAuthenticator(authenticator),
164+
// sourcetool.WithPolicyRepo(opts.policyRepo),
165+
)
166+
if err != nil {
167+
return err
168+
}
169+
170+
epcy, err := srctool.GetRepositoryPolicy(context.Background(), opts.GetRepository())
171+
if err != nil {
172+
return fmt.Errorf("checking for existing policy: %w", err)
173+
}
174+
if epcy != nil {
175+
return fmt.Errorf("repository already has a policy checked into the community repo")
176+
}
177+
178+
// Create the policy, this will open the pull request in the community
179+
// repo if the options say so.
180+
pcy, pr, err := srctool.CreateRepositoryPolicy(
181+
context.Background(), opts.GetRepository(), []*models.Branch{opts.GetBranch()},
182+
)
183+
if err != nil {
184+
return err
185+
}
186+
187+
if err := displayPolicy(opts.repoOptions, pcy); err != nil {
188+
return err
189+
}
190+
191+
if opts.openPullRequest && pr != nil {
192+
fmt.Fprintf(os.Stderr, "\n")
193+
fmt.Fprintf(os.Stderr, "Opened pull request: https://github.com/%s/pulls/%d\n\n", pr.Repo.Path, pr.Number)
194+
}
195+
196+
return nil
197+
},
198+
}
199+
opts.AddFlags(policyViewCmd)
200+
parent.AddCommand(policyViewCmd)
201+
}
202+
203+
func displayPolicy(opts repoOptions, pcy *policy.RepoPolicy) error {
204+
if pcy == nil {
205+
fmt.Println("\n" + w(fmt.Sprintf("✖️ No source policy found for %s/%s", opts.owner, opts.repository)))
206+
fmt.Println("To create and check-in a policy for the repository run:")
207+
fmt.Println()
208+
fmt.Printf(" sourcetool policy create %s/%s\n", opts.owner, opts.repository)
209+
fmt.Println()
210+
return nil
211+
}
212+
213+
data, err := json.MarshalIndent(pcy, "", " ")
214+
if err != nil {
215+
return fmt.Errorf("marshaling policy data: %w", err)
216+
}
217+
218+
fmt.Fprint(os.Stderr, w(fmt.Sprintf("\n🛡️ Source policy for %s/%s:\n\n", opts.owner, opts.repository)))
219+
fmt.Println(string(data))
220+
fmt.Println()
221+
return nil
222+
}

sourcetool/internal/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ controls and much more.
5454
addCheckTag(rootCmd)
5555
addCreatePolicy(rootCmd)
5656
addAuth(rootCmd)
57+
addPolicy(rootCmd)
5758
return rootCmd
5859
}
5960

sourcetool/pkg/sourcetool/options.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ func WithEnforce(enforce bool) ConfigFn {
2525
}
2626
}
2727

28+
func WithCreatePolicyPR(yesno bool) ConfigFn {
29+
return func(t *Tool) error {
30+
t.Options.CreatePolicyPR = yesno
31+
return nil
32+
}
33+
}
34+
2835
func WithUserForkOrg(org string) ConfigFn {
2936
return func(t *Tool) error {
3037
t.Options.UserForkOrg = org

sourcetool/pkg/sourcetool/options/options.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ type Options struct {
1313
UseSSH bool
1414
UpdateRepo bool
1515

16+
CreatePolicyPR bool
17+
1618
// PolicyRepo is the repository where the policies are stored
1719
PolicyRepo string
1820
}
1921

2022
// DefaultOptions holds the default options the tool initializes with
2123
var Default = Options{
22-
PolicyRepo: fmt.Sprintf("%s/%s", policy.SourcePolicyRepoOwner, policy.SourcePolicyRepo),
23-
UseSSH: true,
24+
PolicyRepo: fmt.Sprintf("%s/%s", policy.SourcePolicyRepoOwner, policy.SourcePolicyRepo),
25+
UseSSH: true,
26+
CreatePolicyPR: true,
2427
}

sourcetool/pkg/sourcetool/tool.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (t *Tool) CheckPolicyRepoFork(repo *models.Repository) (bool, error) {
174174
// CreateBranchPolicy creates a repository policy
175175
func (t *Tool) CreateBranchPolicy(ctx context.Context, r *models.Repository, branches []*models.Branch) (*policy.RepoPolicy, error) {
176176
if len(branches) > 1 {
177-
// Chanfe this once we support merging policies
177+
// Change this once we support merging policies
178178
return nil, fmt.Errorf("only one branch is supported at a time")
179179
}
180180
if branches == nil {
@@ -241,3 +241,33 @@ func (t *Tool) createPolicy(r *models.Repository, branch *models.Branch, control
241241
}
242242
return p, nil
243243
}
244+
245+
// GetRepositoryPolicy retrieves the policy of repo from the community
246+
func (t *Tool) GetRepositoryPolicy(ctx context.Context, r *models.Repository) (*policy.RepoPolicy, error) {
247+
pe := policy.NewPolicyEvaluator()
248+
p, _, err := pe.GetPolicy(ctx, r)
249+
if err != nil {
250+
return nil, fmt.Errorf("getting repository policy: %w", err)
251+
}
252+
253+
return p, nil
254+
}
255+
256+
// CreateRepositoryPolicy creates a policy for a repository
257+
func (t *Tool) CreateRepositoryPolicy(ctx context.Context, r *models.Repository, branches []*models.Branch) (*policy.RepoPolicy, *models.PullRequest, error) {
258+
pcy, err := t.CreateBranchPolicy(ctx, r, branches)
259+
if err != nil {
260+
return nil, nil, fmt.Errorf("creating policy for: %w", err)
261+
}
262+
263+
var pr *models.PullRequest
264+
265+
// If the option is set, open the pull request
266+
if t.Options.CreatePolicyPR {
267+
pr, err = t.impl.CreatePolicyPR(t.Authenticator, &t.Options, r, pcy)
268+
if err != nil {
269+
return nil, nil, fmt.Errorf("opening the policy pull request: %w", err)
270+
}
271+
}
272+
return pcy, pr, nil
273+
}

0 commit comments

Comments
 (0)