diff --git a/garnet/go/src/pm/cmd/pm/serve/serve.go b/garnet/go/src/pm/cmd/pm/serve/serve.go index c7706f2240dae1e42637474beac93669b481ad9e..de4143b96bb2e591f1345c2a3e17abac34b15b0a 100644 --- a/garnet/go/src/pm/cmd/pm/serve/serve.go +++ b/garnet/go/src/pm/cmd/pm/serve/serve.go @@ -5,9 +5,12 @@ package serve import ( + "bufio" "compress/gzip" "flag" "fmt" + "io/ioutil" + "log" "net/http" "os" "path/filepath" @@ -20,6 +23,9 @@ import ( "fuchsia.googlesource.com/pm/repo" ) +// server is a default http server only parameterized for tests. +var server http.Server + func Run(cfg *build.Config, args []string) error { fs := flag.NewFlagSet("serve", flag.ExitOnError) repoDir := fs.String("d", "", "(deprecated, use -repo) path to the repository") @@ -31,6 +37,7 @@ func Run(cfg *build.Config, args []string) error { auto := fs.Bool("a", true, "Host auto endpoint for realtime client updates") quiet := fs.Bool("q", false, "Don't print out information about requests") encryptionKey := fs.String("e", "", "Path to a symmetric blob encryption key *UNSAFE*") + publishList := fs.String("p", "", "path to a package list file to be auto-published") fs.Usage = func() { fmt.Fprintf(os.Stderr, "usage: %s serve", filepath.Base(os.Args[0])) @@ -44,16 +51,20 @@ func Run(cfg *build.Config, args []string) error { config.ApplyDefaults() if *repoDir == "" { - *repoDir = filepath.Join(config.RepoDir, "repository") + *repoDir = config.RepoDir } + repoPubDir := filepath.Join(*repoDir, "repository") - fi, err := os.Stat(*repoDir) + repo, err := repo.New(*repoDir) if err != nil { - return fmt.Errorf("repository path %q is not valid: %s", *repoDir, err) + return err + } + if *encryptionKey != "" { + repo.EncryptWith(*encryptionKey) } - if !fi.IsDir() { - return fmt.Errorf("repository path %q is not a directory", *repoDir) + if err := repo.Init(); err != nil && err != os.ErrExist { + return fmt.Errorf("repository at %q is not valid or could not be initialized: %s", *repoDir, err) } if *auto { @@ -63,25 +74,71 @@ func Run(cfg *build.Config, args []string) error { if err != nil { return fmt.Errorf("failed to initialize fsnotify: %s", err) } - timestampPath := filepath.Join(*repoDir, "timestamp.json") - err = w.Add(timestampPath) - if err != nil { + + publishAll := func() { + if *publishList != "" { + f, err := os.Open(*publishList) + if err != nil { + log.Printf("reading package list %q: %s", *publishList, err) + return + } + defer f.Close() + + s := bufio.NewScanner(f) + for s.Scan() { + m := s.Text() + if _, err := os.Stat(m); err == nil { + repo.PublishManifest(m) + if err := w.Add(m); err != nil { + log.Printf("unable to watch %q", m) + } + } else { + log.Printf("unable to publish %q", m) + } + } + if err := repo.CommitUpdates(config.TimeVersioned); err != nil { + log.Printf("committing repo: %s", err) + } + } + } + + timestampPath := filepath.Join(repoPubDir, "timestamp.json") + if err = w.Add(timestampPath); err != nil { return fmt.Errorf("failed to watch %s: %s", timestampPath, err) } + if *publishList != "" { + if err := w.Add(*publishList); err != nil { + return fmt.Errorf("failed to watch %s: %s", *publishList, err) + } + } go func() { - for range w.Events { - fi, err := os.Stat(timestampPath) - if err != nil { - continue + for event := range w.Events { + switch event.Name { + case timestampPath: + fi, err := os.Stat(timestampPath) + if err != nil { + continue + } + as.Broadcast("timestamp.json", fi.ModTime().Format(http.TimeFormat)) + case *publishList: + publishAll() + default: + if err := repo.PublishManifest(event.Name); err != nil { + log.Printf("publishing %q: %s", event.Name, err) + continue + } + if err := repo.CommitUpdates(config.TimeVersioned); err != nil { + log.Printf("committing repo update %q: %s", event.Name, err) + } } - as.Broadcast("timestamp.json", fi.ModTime().Format(http.TimeFormat)) } }() http.Handle("/auto", as) + publishAll() } - dirServer := http.FileServer(http.Dir(*repoDir)) + dirServer := http.FileServer(http.Dir(repoPubDir)) http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/": @@ -93,7 +150,13 @@ func Run(cfg *build.Config, args []string) error { } })) - cs := pmhttp.NewConfigServer(*repoDir, *encryptionKey) + cs := pmhttp.NewConfigServer(func() []byte { + b, err := ioutil.ReadFile(filepath.Join(repoPubDir, "root.json")) + if err != nil { + log.Printf("%s", err) + } + return b + }, *encryptionKey) http.Handle("/config.json", cs) if !*quiet { @@ -101,7 +164,8 @@ func Run(cfg *build.Config, args []string) error { time.Now().Format("2006-01-02 15:04:05"), *repoDir, *listen) } - return http.ListenAndServe(*listen, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + server.Addr = *listen + server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.RequestURI, "/blobs") && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { gw := &pmhttp.GZIPWriter{ w, @@ -117,5 +181,7 @@ func Run(cfg *build.Config, args []string) error { fmt.Printf("%s [pm serve] %d %s\n", time.Now().Format("2006-01-02 15:04:05"), lw.Status, r.RequestURI) } - })) + }) + + return server.ListenAndServe() } diff --git a/garnet/go/src/pm/fswatch/fswatch.go b/garnet/go/src/pm/fswatch/fswatch.go index f8e4dc2a534f20d0e27ba0aca444f163e0f54077..ae2234700a26c6805a724bc5dfa0951b55ba8a51 100644 --- a/garnet/go/src/pm/fswatch/fswatch.go +++ b/garnet/go/src/pm/fswatch/fswatch.go @@ -10,6 +10,6 @@ import ( "github.com/fsnotify/fsnotify" ) -type Watcher fsnotify.Watcher +type Watcher = fsnotify.Watcher var NewWatcher = fsnotify.NewWatcher diff --git a/garnet/go/src/pm/pmhttp/config.go b/garnet/go/src/pm/pmhttp/config.go index 43511e64d54c721b949b4278c2433b1955d15ef5..6b1f6914c841308d70a6eb5c206ca336defe2703 100644 --- a/garnet/go/src/pm/pmhttp/config.go +++ b/garnet/go/src/pm/pmhttp/config.go @@ -10,17 +10,15 @@ import ( "io/ioutil" "log" "net/http" - "os" - "path/filepath" ) type ConfigServer struct { - repoDir string - encryptionKey string + rootKeyFetcher func() []byte + encryptionKey string } -func NewConfigServer(repoDir, encryptionKey string) *ConfigServer { - return &ConfigServer{repoDir: repoDir, encryptionKey: encryptionKey} +func NewConfigServer(rootKeyFetcher func() []byte, encryptionKey string) *ConfigServer { + return &ConfigServer{rootKeyFetcher: rootKeyFetcher, encryptionKey: encryptionKey} } func (c *ConfigServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -31,35 +29,6 @@ func (c *ConfigServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { repoUrl := fmt.Sprintf("%s%s", scheme, r.Host) - var signedKeys struct { - Signed struct { - Keys map[string]struct { - Keytype string - Keyval struct { - Public string - } - } - Roles struct { - Root struct { - Keyids []string - } - Threshold int - } - } - } - f, err := os.Open(filepath.Join(c.repoDir, "root.json")) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Printf("root.json missing or unreadable: %s", err) - return - } - defer f.Close() - if err := json.NewDecoder(f).Decode(&signedKeys); err != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Printf("root.json parsing error: %s", err) - return - } - cfg := struct { ID string RepoURL string @@ -101,8 +70,15 @@ func (c *ConfigServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { copy(cfg.BlobKey.Data[:], keyBytes) } - for _, id := range signedKeys.Signed.Roles.Root.Keyids { - k := signedKeys.Signed.Keys[id] + var keys signedKeys + if err := json.Unmarshal(c.rootKeyFetcher(), &keys); err != nil { + log.Printf("root.json parsing error: %s", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + for _, id := range keys.Signed.Roles.Root.Keyids { + k := keys.Signed.Keys[id] cfg.RootKeys = append(cfg.RootKeys, struct{ Type, Value string }{ Type: k.Keytype, Value: k.Keyval.Public, @@ -110,3 +86,20 @@ func (c *ConfigServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } json.NewEncoder(w).Encode(cfg) } + +type signedKeys struct { + Signed struct { + Keys map[string]struct { + Keytype string + Keyval struct { + Public string + } + } + Roles struct { + Root struct { + Keyids []string + } + Threshold int + } + } +}