Skip to content

Commit a85c462

Browse files
committed
Add policyctl check-update
Signed-off-by: Adolfo García Veytia (Puerco) <puerco@carabiner.dev>
1 parent 89d7036 commit a85c462

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

internal/cmd/check_update.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// SPDX-FileCopyrightText: Copyright 2026 Carabiner Systems, Inc
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"sort"
10+
"text/tabwriter"
11+
12+
"github.com/carabiner-dev/policy"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
func addCheckUpdate(parentCmd *cobra.Command) {
17+
cmd := &cobra.Command{
18+
Short: "check policy references for available updates",
19+
Use: "check-update [flags] <location> [<location>...]",
20+
Example: fmt.Sprintf(" %s check-update ./policies", appname),
21+
SilenceUsage: false,
22+
SilenceErrors: true,
23+
Long: `Check one or more policy source locations for external references
24+
that have updates available.
25+
26+
Each location may be a policy file, a directory containing policies, or a
27+
VCS locator (e.g. git+https://github.com/org/repo@ref#path). Directory
28+
locations are walked and every policy, policy set, or policy group file
29+
discovered is inspected for external references.
30+
31+
For every reference, the upstream repository is queried for its latest
32+
commit. If the referenced policy's content has changed, an entry is
33+
reported showing the old and new commits and digests.`,
34+
PersistentPreRunE: initLogging,
35+
RunE: func(cmd *cobra.Command, args []string) error {
36+
if len(args) == 0 {
37+
return fmt.Errorf("at least one location is required")
38+
}
39+
cmd.SilenceUsage = true
40+
41+
updates, err := policy.NewUpdater().CheckUpdates(args...)
42+
if err != nil {
43+
return err
44+
}
45+
46+
if len(updates) == 0 {
47+
fmt.Fprintln(os.Stderr, "no policy references have updates available")
48+
return nil
49+
}
50+
51+
printUpdatesTable(os.Stdout, updates)
52+
return nil
53+
},
54+
}
55+
parentCmd.AddCommand(cmd)
56+
}
57+
58+
func printUpdatesTable(out *os.File, updates map[string][]*policy.RefUpdate) {
59+
files := make([]string, 0, len(updates))
60+
for f := range updates {
61+
files = append(files, f)
62+
}
63+
sort.Strings(files)
64+
65+
tw := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0)
66+
fmt.Fprintln(tw, "FILE\tREFERENCE\tOLD URI\tNEW URI\tOLD DIGEST\tNEW DIGEST")
67+
for _, f := range files {
68+
for _, u := range updates[f] {
69+
name := u.Old.GetId()
70+
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\n",
71+
f, name,
72+
u.Old.GetLocation().GetUri(),
73+
u.New.GetLocation().GetUri(),
74+
short(u.Old.GetLocation().GetDigest()["sha256"]),
75+
short(u.New.GetLocation().GetDigest()["sha256"]),
76+
)
77+
}
78+
}
79+
_ = tw.Flush()
80+
}
81+
82+
func short(s string) string {
83+
if len(s) > 12 {
84+
return s[:12]
85+
}
86+
return s
87+
}

internal/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func New() *cobra.Command {
4040
addSign(rootCmd)
4141
addKeys(rootCmd)
4242
addUpdate(rootCmd)
43+
addCheckUpdate(rootCmd)
4344
addVerify(rootCmd)
4445
addTest(rootCmd)
4546
addDoc(rootCmd)

0 commit comments

Comments
 (0)