main: save state of weldr server to /var/lib
This commit is contained in:
parent
73655f600e
commit
11af3f0c0b
3 changed files with 124 additions and 35 deletions
61
main.go
61
main.go
|
|
@ -3,16 +3,20 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
|
||||
"osbuild-composer/rpmmd"
|
||||
"osbuild-composer/weldr"
|
||||
)
|
||||
|
||||
const StateFile = "/var/lib/osbuild-composer/weldr-state.json"
|
||||
|
||||
func main() {
|
||||
var verbose bool
|
||||
flag.BoolVar(&verbose, "v", false, "Print access log")
|
||||
|
|
@ -44,9 +48,28 @@ func main() {
|
|||
logger = log.New(os.Stdout, "", 0)
|
||||
}
|
||||
|
||||
api := weldr.New(repo, packages, logger)
|
||||
server := http.Server{Handler: api}
|
||||
err = os.MkdirAll("/var/lib/osbuild-composer", 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
state, err := ioutil.ReadFile(StateFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Fatalf("cannot read state: %v", err)
|
||||
}
|
||||
|
||||
stateChannel := make(chan []byte, 10)
|
||||
api := weldr.New(repo, packages, logger, state, stateChannel)
|
||||
go func() {
|
||||
for {
|
||||
err := writeFileAtomically(StateFile, <-stateChannel, 0755)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot write state: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
server := http.Server{Handler: api}
|
||||
shutdownDone := make(chan struct{}, 1)
|
||||
go func() {
|
||||
channel := make(chan os.Signal, 1)
|
||||
|
|
@ -63,3 +86,37 @@ func main() {
|
|||
|
||||
<-shutdownDone
|
||||
}
|
||||
|
||||
func writeFileAtomically(filename string, data []byte, mode os.FileMode) error {
|
||||
dir, name := filepath.Dir(filename), filepath.Base(filename)
|
||||
|
||||
tmpfile, err := ioutil.TempFile(dir, name + "-*.tmp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tmpfile.Write(data)
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
err = tmpfile.Chmod(mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tmpfile.Close()
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Rename(tmpfile.Name(), filename)
|
||||
if err != nil {
|
||||
os.Remove(tmpfile.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
22
weldr/api.go
22
weldr/api.go
|
|
@ -22,22 +22,24 @@ type API struct {
|
|||
router *httprouter.Router
|
||||
}
|
||||
|
||||
func New(repo rpmmd.RepoConfig, packages rpmmd.PackageList, logger *log.Logger) *API {
|
||||
func New(repo rpmmd.RepoConfig, packages rpmmd.PackageList, logger *log.Logger, initialState []byte, stateChannel chan<- []byte) *API {
|
||||
api := &API{
|
||||
store: newStore(),
|
||||
store: newStore(initialState, stateChannel),
|
||||
repo: repo,
|
||||
packages: packages,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
// sample blueprint
|
||||
api.store.pushBlueprint(blueprint{
|
||||
Name: "example",
|
||||
Description: "An Example",
|
||||
Version: "1",
|
||||
Packages: []blueprintPackage{{"httpd", "2.*"}},
|
||||
Modules: []blueprintPackage{},
|
||||
})
|
||||
// sample blueprint on first run
|
||||
if initialState == nil {
|
||||
api.store.pushBlueprint(blueprint{
|
||||
Name: "example",
|
||||
Description: "An Example",
|
||||
Version: "1",
|
||||
Packages: []blueprintPackage{{"httpd", "2.*"}},
|
||||
Modules: []blueprintPackage{},
|
||||
})
|
||||
}
|
||||
|
||||
api.router = httprouter.New()
|
||||
api.router.RedirectTrailingSlash = false
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package weldr
|
||||
|
||||
import (
|
||||
"log"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
|
@ -9,7 +11,8 @@ type store struct {
|
|||
Blueprints map[string]blueprint `json:"blueprints"`
|
||||
Workspace map[string]blueprint `json:"workspace"`
|
||||
|
||||
mu sync.RWMutex // protects all fields
|
||||
mu sync.RWMutex // protects all fields
|
||||
stateChannel chan<- []byte
|
||||
}
|
||||
|
||||
type blueprint struct {
|
||||
|
|
@ -25,10 +28,41 @@ type blueprintPackage struct {
|
|||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
func newStore() *store {
|
||||
return &store{
|
||||
Blueprints: make(map[string]blueprint),
|
||||
Workspace: make(map[string]blueprint),
|
||||
func newStore(initialState []byte, stateChannel chan<- []byte) *store {
|
||||
var s store
|
||||
|
||||
if initialState != nil {
|
||||
err := json.Unmarshal(initialState, &s)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid initial state: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if s.Blueprints == nil {
|
||||
s.Blueprints = make(map[string]blueprint)
|
||||
}
|
||||
if s.Workspace == nil {
|
||||
s.Workspace = make(map[string]blueprint)
|
||||
}
|
||||
s.stateChannel = stateChannel
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *store) change(f func()) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
f()
|
||||
|
||||
if s.stateChannel != nil {
|
||||
serialized, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
// we ought to know all types that go into the store
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s.stateChannel <- serialized
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,31 +109,27 @@ func (s *store) getBlueprint(name string, bp *blueprint, changed *bool) bool {
|
|||
}
|
||||
|
||||
func (s *store) pushBlueprint(bp blueprint) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
delete(s.Workspace, bp.Name)
|
||||
s.Blueprints[bp.Name] = bp
|
||||
s.change(func() {
|
||||
delete(s.Workspace, bp.Name)
|
||||
s.Blueprints[bp.Name] = bp
|
||||
})
|
||||
}
|
||||
|
||||
func (s *store) pushBlueprintToWorkspace(bp blueprint) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.Workspace[bp.Name] = bp
|
||||
s.change(func() {
|
||||
s.Workspace[bp.Name] = bp
|
||||
})
|
||||
}
|
||||
|
||||
func (s *store) deleteBlueprint(name string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
delete(s.Workspace, name)
|
||||
delete(s.Blueprints, name)
|
||||
s.change(func() {
|
||||
delete(s.Workspace, name)
|
||||
delete(s.Blueprints, name)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *store) deleteBlueprintFromWorkspace(name string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
delete(s.Workspace, name)
|
||||
s.change(func() {
|
||||
delete(s.Workspace, name)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue