Skip to content

Commit

Permalink
Fix container fail to start after PR4981 #4987
Browse files Browse the repository at this point in the history
ref DEV-2338
  • Loading branch information
tung2744 authored Jan 17, 2025
2 parents 062db4a + 72e414c commit 17aa505
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 162 deletions.
153 changes: 7 additions & 146 deletions pkg/lib/web/embedded_resource.go
Original file line number Diff line number Diff line change
@@ -1,166 +1,27 @@
package web

import (
"encoding/json"
"io"
"net/http"
"os"
"path"
"sync/atomic"

"gopkg.in/fsnotify.v1"

"github.com/authgear/authgear-server/pkg/util/resource"
)

const defaultResourceDir = "resources/authgear/generated"
const defaultManifestName = "manifest.json"

type Manifest struct {
ResourceDir string
Name string
content atomic.Value
type GlobalEmbeddedResourceManagerImpl interface {
AssetName(key string) (name string, err error)
Open(name string) (http.File, error)
}

type GlobalEmbeddedResourceManager struct {
Manifest *Manifest
watcher *fsnotify.Watcher
}

type ManifestContext struct {
Content map[string]string
}

func NewDefaultGlobalEmbeddedResourceManager() (*GlobalEmbeddedResourceManager, error) {
return NewGlobalEmbeddedResourceManager(&Manifest{
ResourceDir: defaultResourceDir,
Name: defaultManifestName,
})
}

func NewGlobalEmbeddedResourceManager(manifest *Manifest) (*GlobalEmbeddedResourceManager, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}

m := &GlobalEmbeddedResourceManager{
Manifest: &Manifest{
ResourceDir: manifest.ResourceDir,
Name: manifest.Name,
},
watcher: watcher,
}

err = m.setupWatch(nil)
if err != nil {
return nil, err
}

err = m.reload()
if err != nil {
return nil, err
}

go m.watch()

return m, nil
}

func (m *GlobalEmbeddedResourceManager) loadManifest() (map[string]string, error) {
jsonFile, err := os.Open(m.ManifestFilePath())
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, err
}
defer jsonFile.Close()

byteValue, _ := io.ReadAll(jsonFile)

var result map[string]string
_ = json.Unmarshal([]byte(byteValue), &result)

return result, nil
Impl GlobalEmbeddedResourceManagerImpl
}

func (m *GlobalEmbeddedResourceManager) setupWatch(event *fsnotify.Event) (err error) {
if event == nil {
err = m.watcher.Add(m.ManifestFilePath())
if os.IsNotExist(err) {
err = m.watcher.Add(m.Manifest.ResourceDir)
}
return
}

switch event.Op {
case fsnotify.Create, fsnotify.Write:
_ = m.watcher.Remove(m.Manifest.ResourceDir)
err = m.watcher.Add(m.ManifestFilePath())
case fsnotify.Remove:
err = m.watcher.Add(m.Manifest.ResourceDir)
}

return
}

func (m *GlobalEmbeddedResourceManager) watch() {
for {
select {
case event, ok := <-m.watcher.Events:
if !ok {
return
}

if event.Name != m.ManifestFilePath() {
break
}

_ = m.setupWatch(&event)
_ = m.reload()

case _, ok := <-m.watcher.Errors:
if !ok {
return
}
}
}
}

func (m *GlobalEmbeddedResourceManager) reload() error {
newManifest, err := m.loadManifest()
if err != nil {
return err
}

manifestCtx := &ManifestContext{
Content: newManifest,
}
m.Manifest.content.Store(manifestCtx)
return nil
}

func (m *GlobalEmbeddedResourceManager) ManifestFilePath() string {
return path.Join(m.Manifest.ResourceDir, m.Manifest.Name)
}

func (m *GlobalEmbeddedResourceManager) GetManifestContext() *ManifestContext {
return m.Manifest.content.Load().(*ManifestContext)
}

func (m *GlobalEmbeddedResourceManager) Close() error {
return m.watcher.Close()
}
var _ GlobalEmbeddedResourceManagerImpl = (*GlobalEmbeddedResourceManager)(nil)

func (m *GlobalEmbeddedResourceManager) AssetName(key string) (name string, err error) {
manifest := m.GetManifestContext().Content
if val, ok := manifest[key]; ok {
return val, nil
}
return "", resource.ErrResourceNotFound
return m.Impl.AssetName(key)
}

func (m *GlobalEmbeddedResourceManager) Open(name string) (http.File, error) {
fs := http.Dir(m.Manifest.ResourceDir)
return fs.Open(name)
return m.Impl.Open(name)
}
18 changes: 18 additions & 0 deletions pkg/lib/web/embedded_resource_authgeardev.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//go:build authgeardev
// +build authgeardev

package web

func NewDefaultGlobalEmbeddedResourceManager() (*GlobalEmbeddedResourceManager, error) {
impl, err := NewGlobalEmbeddedResourceManagerWorkdir(&globalEmbeddedResourceManagerManifest{
ResourceDir: defaultResourceDir,
Name: defaultManifestName,
})
if err != nil {
return nil, err
}

return &GlobalEmbeddedResourceManager{
Impl: impl,
}, nil
}
68 changes: 68 additions & 0 deletions pkg/lib/web/embedded_resource_embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package web

import (
"embed"
"encoding/json"
"io"
"io/fs"
"net/http"
"path"

"github.com/authgear/authgear-server/pkg/util/resource"
)

type GlobalEmbeddedResourceManagerEmbed struct {
EmbedFS embed.FS
EmbedFSRoot string
ManifestFilenameRelativeToEmbedFSRoot string
Manifest map[string]string
}

var _ GlobalEmbeddedResourceManagerImpl = (*GlobalEmbeddedResourceManagerEmbed)(nil)

type NewGlobalEmbeddedResourceManagerEmbedOptions struct {
EmbedFS embed.FS
EmbedFSRoot string
ManifestFilenameRelativeToEmbedFSRoot string
}

func NewGlobalEmbeddedResourceManagerEmbed(opts NewGlobalEmbeddedResourceManagerEmbedOptions) (*GlobalEmbeddedResourceManagerEmbed, error) {
p := path.Join(opts.EmbedFSRoot, opts.ManifestFilenameRelativeToEmbedFSRoot)

f, err := opts.EmbedFS.Open(p)
if err != nil {
return nil, err
}
defer f.Close()

byteValue, err := io.ReadAll(f)
if err != nil {
return nil, err
}

var manifest map[string]string
_ = json.Unmarshal([]byte(byteValue), &manifest)

return &GlobalEmbeddedResourceManagerEmbed{
EmbedFS: opts.EmbedFS,
EmbedFSRoot: opts.EmbedFSRoot,
ManifestFilenameRelativeToEmbedFSRoot: opts.ManifestFilenameRelativeToEmbedFSRoot,
Manifest: manifest,
}, nil
}

func (m *GlobalEmbeddedResourceManagerEmbed) AssetName(key string) (name string, err error) {
if val, ok := m.Manifest[key]; ok {
return val, nil
}
return "", resource.ErrResourceNotFound
}

func (m *GlobalEmbeddedResourceManagerEmbed) Open(name string) (http.File, error) {
fsFileSystem, err := fs.Sub(m.EmbedFS, m.EmbedFSRoot)
if err != nil {
return nil, err
}
httpFileSystem := http.FS(fsFileSystem)
return httpFileSystem.Open(name)
}
54 changes: 54 additions & 0 deletions pkg/lib/web/embedded_resource_embed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package web

import (
"embed"
"errors"
"io"
"io/fs"
"testing"

. "github.com/smartystreets/goconvey/convey"
)

//go:embed testdata/embed
var testEmbedFS embed.FS

func TestGlobalEmbeddedResourceManagerEmbed(t *testing.T) {
Convey("GlobalEmbeddedResourceManagerEmbed", t, func() {
m, err := NewGlobalEmbeddedResourceManagerEmbed(NewGlobalEmbeddedResourceManagerEmbedOptions{
EmbedFS: testEmbedFS,
EmbedFSRoot: "testdata/embed/a",
ManifestFilenameRelativeToEmbedFSRoot: "manifest.json",
})
So(err, ShouldBeNil)

Convey("basic AssetName() and Open()", func() {
name, err := m.AssetName("index.js")
So(err, ShouldBeNil)
So(name, ShouldEqual, "index.deadbeef.js")

f, err := m.Open(name)
So(err, ShouldBeNil)
defer f.Close()

contents, err := io.ReadAll(f)
So(err, ShouldBeNil)
So(string(contents), ShouldEqual, "console.log(\"hello\");\n")
})

Convey("unknown asset name", func() {
_, err := m.AssetName("nonsense")
So(err, ShouldBeError, "specified resource is not configured")
})

Convey("reading parent directory", func() {
_, err := m.Open("../evil")
So(errors.Is(err, fs.ErrInvalid), ShouldBeTrue)
})

Convey("reading non-existent file", func() {
_, err := m.Open("notfound")
So(errors.Is(err, fs.ErrNotExist), ShouldBeTrue)
})
})
}
23 changes: 23 additions & 0 deletions pkg/lib/web/embedded_resource_noauthgeardev.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//go:build !authgeardev
// +build !authgeardev

package web

import (
"github.com/authgear/authgear-server"
)

func NewDefaultGlobalEmbeddedResourceManager() (*GlobalEmbeddedResourceManager, error) {
impl, err := NewGlobalEmbeddedResourceManagerEmbed(NewGlobalEmbeddedResourceManagerEmbedOptions{
EmbedFS: runtimeresource.EmbedFS_resources_authgear,
EmbedFSRoot: defaultResourceDir,
ManifestFilenameRelativeToEmbedFSRoot: defaultManifestName,
})
if err != nil {
return nil, err
}

return &GlobalEmbeddedResourceManager{
Impl: impl,
}, nil
}
Loading

0 comments on commit 17aa505

Please sign in to comment.