-
Notifications
You must be signed in to change notification settings - Fork 111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Static assets endpoint #5063
Static assets endpoint #5063
Conversation
Features yaml.Node `yaml:"features"` | ||
PublicPaths []string `yaml:"public_paths"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Features yaml.Node `yaml:"features"` | |
PublicPaths []string `yaml:"public_paths"` | |
Features yaml.Node `yaml:"features"` | |
// Paths to expose over HTTP (defaults to ./public) | |
PublicPaths []string `yaml:"public_paths"` |
if len(tmp.PublicPaths) == 0 { | ||
tmp.PublicPaths = []string{"public"} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move to around line 168 – it's easier to navigate if the properties are handled in the same order as they are declared
runtime/drivers/admin/admin.go
Outdated
cache := make(map[string][]byte) | ||
for _, p := range h.cachedPaths { | ||
p = filepath.Join(h.projPath, p) | ||
_, err = os.Stat(p) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
continue | ||
} | ||
return nil, err | ||
} | ||
|
||
err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error { | ||
if err != nil { | ||
return err | ||
} | ||
if !info.IsDir() { | ||
b, err := os.ReadFile(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
rel, err := filepath.Rel(h.projPath, path) | ||
if err != nil { | ||
return err | ||
} | ||
cache[strings.TrimLeft(rel, "/")] = b | ||
} | ||
return nil | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
h.assetsCache = cache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a useful cache. The place where caching is nice is to be able to serve assets immediately when the runtime starts, even if Github takes a few minutes to clone. This means the cached files would need to be stored on the persistent disk, and be readable before the first call to cloneOrPull
.
Also, we should not eagerly cache the assets in memory – it can lead to excessive memory usage if there are larger files. Relying on the OS file system page cache is more than adequate for our performance requirements here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder how a user can get into a state where there's cache on disk but no cloned repo? Because cache can be created from a clone repo.
And why would user like to delete a cloned repo leaving just the cache on disk?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The repo is currently cloned into a temp directory which will not exist after a process restart:
rill/runtime/drivers/admin/admin.go
Line 356 in 1983fb1
h.repoPath, err = os.MkdirTemp(h.config.TempDir, "admin_driver_repo") |
It might be fine to cache the full repo on the persistent disk – that just isn't what it's doing now.
runtime/drivers/file/repo.go
Outdated
cache := make(map[string][]byte) | ||
for _, p := range c.cachedPaths { | ||
p = filepath.Join(c.root, p) | ||
_, err := os.Stat(p) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
continue | ||
} | ||
return err | ||
} | ||
err = filepath.Walk(p, func(path string, info os.FileInfo, err error) error { | ||
if err != nil { | ||
return err | ||
} | ||
if !info.IsDir() { | ||
b, err := os.ReadFile(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
rel, err := filepath.Rel(c.root, path) | ||
if err != nil { | ||
return err | ||
} | ||
cache[strings.TrimLeft(rel, "/")] = b | ||
} | ||
return nil | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
c.cacheMutex.Lock() | ||
defer c.cacheMutex.Unlock() | ||
c.assetsCache = cache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file
driver does not have a cloning latency like Github has, so there is no need for caching assets in it.
paths := parser.RillYAML.PublicPaths | ||
if len(paths) == 0 { | ||
paths = []string{"public"} | ||
} | ||
repo, release, err := r.C.Runtime.Repo(ctx, parser.InstanceID) | ||
if err != nil { | ||
return err | ||
} | ||
defer release() | ||
repo.SetCachedPaths(paths) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be handled inside UpdateInstanceWithRillYAML
runtime/server/assets.go
Outdated
paths := repo.GetCachedPaths() | ||
allowed := false | ||
for _, p := range paths { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's too implicit to rely on the repo's cached paths as an allow list. It only works as long as that feature is only used for PublicPaths
.
Instead, I would suggest explicitly adding PublicPaths
as a property on Instance
, assigning it in UpdateInstanceWithRillYAML
, and checking it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it could just pass the paths to cache, no need to pass the full instance. But as discussed above, it might be fine just to cache the whole repo on a persistent disk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would still suggest to omit caching from this PR since it's not so important
runtime/server/assets.go
Outdated
paths := repo.GetCachedPaths() | ||
allowed := false | ||
for _, p := range paths { | ||
if strings.HasPrefix(strings.TrimLeft(path, ","), strings.TrimLeft(p, ",")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- This is not a safe way to check the path. For example, if
/public
is the public directory, it will match/publications/secret.txt
. - Why is it necessary to trim commas from the paths?
runtime/server/assets.go
Outdated
} | ||
} | ||
if !allowed { | ||
return fmt.Errorf("path is not allowed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should return a httputil.Error
with an appropriate status code
I would suggest completing this PR without caching support and optionally taking it up in a follow up PR. |
// Paths to expose over HTTP (defaults to ./public) | ||
PublicPaths []string `db:"public_paths"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to add migrations and handling for it in runtime/drivers/sqlite
(can search for feature_flags
to find locations)
if len(parser.RillYAML.PublicPaths) == 0 { | ||
parser.RillYAML.PublicPaths = []string{"public"} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be removed since the default is assigned in parse_rillyaml.go
runtime/drivers/file/file.go
Outdated
@@ -58,6 +58,7 @@ type configProperties struct { | |||
// a smaller subset of relevant parts of rill.yaml | |||
type rillYAML struct { | |||
IgnorePaths []string `yaml:"ignore_paths"` | |||
CachedPaths []string `yaml:"public_paths"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CachedPaths []string `yaml:"public_paths"` | |
PublicPaths []string `yaml:"public_paths"` |
@egor-ryashin After this PR merges, can you also update in #team-applications about the new endpoint? So that they can wire it up to the custom dashboard components. |
Files can be accessed through http endpoint, the template request:
For example, if repo has the following structure:
then requesting
public/geo.json
can be done with: