blob: c7e823e25bb7caee6357fc8c5a8628bf399ce0f2 [file] [log] [blame]
Serge Bazanski77b87a62023-04-03 15:24:27 +02001package main
2
3import (
4 "fmt"
5 "log"
6 "net/url"
7 "path/filepath"
8 "strings"
9
10 "cloud.google.com/go/storage"
11 "github.com/spf13/cobra"
12
13 "source.monogon.dev/build/toolbase"
14)
15
16var (
17 flagDeep bool
18 flagMirrorBucketName string
19 flagMirrorBucketSubdir string
20)
21
22var rootCmd = &cobra.Command{
23 Use: "mirror",
24 Short: "Developer/CI tool to make sure our RPM mirror for the sandboxroot is up to date",
25 SilenceUsage: true,
26}
27
28// ourMirrorURL returns a fully formed URL-string to our mirror (as defined by
29// flags), optionally appending the given parts as file path parts.
30func ourMirrorURL(parts ...string) string {
31 u := url.URL{}
32 u.Scheme = "https"
33 u.Host = "storage.googleapis.com"
34
35 path := []string{
36 flagMirrorBucketName,
37 flagMirrorBucketSubdir,
38 }
39 path = append(path, parts...)
40 u.Path = filepath.Join(path...)
41 return u.String()
42}
43
44// progress is used to notify the user about operational progress.
45func progress(done, total int) {
46 fmt.Printf("%d/%d files done...\r", done, total)
47}
48
49func checkMirrorURLs(rpms []*rpmDef) error {
50 log.Printf("Checking all RPMs are using our mirror...")
51 allCorrect := true
52 for _, rpm := range rpms {
53 urls := rpm.urls
54
55 haveOur := false
56 haveExternal := false
57 for _, u := range urls {
58 if strings.HasPrefix(u.String(), ourMirrorURL()) {
59 haveOur = true
60 } else {
61 haveExternal = true
62 }
63 if haveOur && haveExternal {
64 break
65 }
66 }
67 if !haveOur {
68 allCorrect = false
69 log.Printf("RPM %s does not contain our mirror in its URLs", rpm.name)
70 }
71 if !haveExternal {
72 allCorrect = false
73 log.Printf("RPM %s does not contain any upstream mirror in its URLs", rpm.name)
74 }
75 }
76 if !allCorrect {
77 return fmt.Errorf("some RPMs have incorrect mirror urls")
78 }
79 return nil
80}
81
82func getRepositoriesBzl() string {
83 ws, err := toolbase.WorkspaceDirectory()
84 if err != nil {
85 log.Fatalf("Failed to figure out workspace location: %v", err)
86 }
87 return filepath.Join(ws, "third_party/sandboxroot/repositories.bzl")
88}
89
90var checkCmd = &cobra.Command{
91 Use: "check",
92 Short: "Check that everything is okay (without performing actual mirroring)",
93 RunE: func(cmd *cobra.Command, args []string) error {
94 path := getRepositoriesBzl()
95 rpms, err := getBazelDNFFiles(path)
96 if err != nil {
97 return fmt.Errorf("could not get RPMs from %s: %v", path, err)
98 }
99
100 if err := checkMirrorURLs(rpms); err != nil {
101 return err
102 }
103
104 if !flagDeep {
105 log.Printf("Checking if all files are present on mirror... (use --deep to download and check hashes)")
106 } else {
107 log.Printf("Verifying contents of all mirrored files...")
108 }
109
110 hasAll := true
111 for i, rpm := range rpms {
112 has, err := rpm.validateOurs(cmd.Context(), flagDeep)
113 if err != nil {
114 return fmt.Errorf("checking %s failed: %v", rpm.name, err)
115 }
116 if !has {
117 log.Printf("Missing %s in mirror", rpm.name)
118 hasAll = false
119 }
120 progress(i+1, len(rpms))
121 }
122 if !hasAll {
123 return fmt.Errorf("some packages missing in mirror, run `mirror sync`")
124 } else {
125 log.Printf("All good.")
126 }
127
128 return nil
129 },
130}
131
132var syncCmd = &cobra.Command{
133 Use: "sync",
134 Short: "Mirror all missing dependencies",
135 Long: `
136Check existence of (or download and verify when --deep) of every file in our
137mirror and upload it if it's missing. If an upload occured, a full re-download
138will be performed for verification.
139`,
140 RunE: func(cmd *cobra.Command, args []string) error {
141 ctx := cmd.Context()
142
143 path := getRepositoriesBzl()
144 rpms, err := getBazelDNFFiles(path)
145 if err != nil {
146 return fmt.Errorf("could not get RPMs from %s: %v", path, err)
147 }
148
149 if err := checkMirrorURLs(rpms); err != nil {
150 return err
151 }
152
153 client, err := storage.NewClient(ctx)
154 if err != nil {
155 if strings.Contains(err.Error(), "could not find default credentials") {
156 log.Printf("Try running gcloud auth application-default login --no-browser")
157 }
158 return fmt.Errorf("could not build google cloud storage client: %v", err)
159 }
160 bucket := client.Bucket(flagMirrorBucketName)
161
162 if !flagDeep {
163 log.Printf("Checking for any missing files...")
164 } else {
165 log.Printf("Verifying all files and uploading if missing or corrupted...")
166 }
167
168 for i, rpm := range rpms {
169 has, err := rpm.validateOurs(ctx, flagDeep)
170 if err != nil {
171 return err
172 }
173 if !has {
174 log.Printf("Mirroring %s...", rpm.name)
175 if err := rpm.mirrorToOurs(ctx, bucket); err != nil {
176 return err
177 }
178 log.Printf("Verifying %s...", rpm.name)
179 has, err = rpm.validateOurs(ctx, true)
180 if err != nil {
181 return err
182 }
183 if !has {
184 return fmt.Errorf("post-mirror validation of %s failed", rpm.name)
185 }
186 }
187 progress(i+1, len(rpms))
188 }
189
190 log.Printf("All good.")
191 return nil
192 },
193}
194
195func main() {
196 rootCmd.PersistentFlags().StringVar(&flagMirrorBucketName, "bucket_name", "monogon-infra-public", "Name of GCS bucket to mirror into.")
197 rootCmd.PersistentFlags().StringVar(&flagMirrorBucketSubdir, "bucket_subdir", "mirror", "Subpath in bucket to upload data to.")
198 rootCmd.PersistentFlags().BoolVar(&flagDeep, "deep", false, "Always download files fully during check/sync to make sure the SHA256 matches.")
199 rootCmd.AddCommand(checkCmd)
200 rootCmd.AddCommand(syncCmd)
201 rootCmd.Execute()
202
203}