-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Add pkger source driver support #377
Conversation
As go-bindata has been abandoned [1] there are open requests, #116, for alternative sources with similar functionality. The Buffalo project [2] created packr and recently pkger [3] was announced [4] with the intention to supersede packr. This change adds support for using pkger as a source. The implementation relies on httpfs.PartialDriver for pretty much all functionality. [1] jteeuwen/go-bindata#5 [2] https://gobuffalo.io/ [3] https://github.com/markbates/pkger [4] https://blog.gobuffalo.io/introducing-pkger-static-file-embedding-in-go-1ce76dc79c65
Pull Request Test Coverage Report for Build 755
💛 - Coveralls |
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.
Thanks for the PR; it looks like a great start!
source/pkger/pkger.go
Outdated
|
||
// Instance is an implementation of http.FileSystem backed by an instance of | ||
// pkging.Pkger. | ||
type Instance struct { |
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.
Rename Instance
to Pkger
to improve readability
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.
Sure I don't mind. FWIW I named it Instance
to avoid stuttering with pkger.Pkger
.
source/pkger/pkger.go
Outdated
} | ||
|
||
// WithInstance returns a source.Driver that is backed by an Instance. | ||
func WithInstance(instance interface{}) (source.Driver, error) { |
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.
Use Pkger
instead of interface{}
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.
Will do. I just followed the precedence of existing source driver go_bindata
.
source/pkger/pkger.go
Outdated
return f.(http.File), nil | ||
} | ||
|
||
type driver struct { |
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.
Why use a separate driver
struct? You can embed httpfs.PartialDriver
into Pkger
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.
Because the driver and the instance requires Open
methods of different signatures.
source.Driver
requiresOpen(url string) (source.Driver, error)
.http.FileSystem
requiresOpen(name string) (http.File, error)
.
It would've been nice if pkging.Pkger
implemented http.FileSystem
but it turns out in fact it does not. I might be missing something but I need to implement http.FileSystem
to be able to pass it to the partial driver Init(fs http.FileSystem, path string) error
method.
Pkger
(previously Instance
) wraps pkging.Pkger
and implements http.FileSystem
so that it can be passed to the partial driver.
driver
implements the source.Driver
interface by providing its Open
method.
If this is all unclear I'm happy to rework it somehow but you will have to provide some guidance on what you think makes more sense.
I'm also curious to know if I'm just missing something.
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.
You can use one struct by not embedding pkging.Pkger
.
e.g.
type Pkger struct {
httpfs.PartialDriver
pkger pkging.Pkger
}
It would've been nice if
pkging.Pkger
implementedhttp.FileSystem
but it turns out in fact it does not.
Pkger
(previouslyInstance
) wrapspkging.Pkger
and implementshttp.FileSystem
so that it can be passed to the partial driver.
You can pass Pkger.pkger
to Init()
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.
Looks like it does
The signatures are different. It does't return http.File
but pkging.File
. It's subtle but means
./pkger.go:56:28: cannot use instance.Pkger (type pkging.Pkger) as type http.FileSystem in argument to ds.PartialDriver.Init:
pkging.Pkger does not implement http.FileSystem (wrong type for Open method)
have Open(string) (pkging.File, error)
want Open(string) (http.File, error)
What am I missing? I still don't see how it would work by simply not embedding pkging.Pkger
.
If we want Pkger
to be the driver and look like
type Pkger struct {
httpfs.PartialDriver
pkger pkging.Pkger
}
I'm not against that but I think we need to wrap Pkger.pkger
to implement http.FileSystem
. Another approach I can think of is to have
type fsFunc func(name string) (http.File, error)
func (f fsFunc) Open(name string) (http.File, error) {
return f(name)
}
and then wrap pkging.Pkger
just before calling Init
like so
fs := fsFunc(func(name string) (http.File, error) {
f, err := instance.pkger.Open(name)
if err != nil {
return nil, err
}
return f.(http.File), nil
})
if err := instace.Init(fs, instance.Path); err != nil {
What about that?
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.
Thinking about this a bit more I'm honestly wondering if it's worth adding this driver at all since it essentially only wraps pkging.Pkger
in http.FileSystem
. It might make sense to just put it on the user to wrap it themselves and instead use httpfs.New
directly. What do you think about that?
For packr.Box
, the other packager, it should be even easier since it alreay implements http.FileSystem
, so the user can pass it straight in.
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 signatures are different. It does't return
http.File
butpkging.File
It's subtle but means ...
Oh wow, didn't notice that! That's annoying... Not sure why they went that route. At a quick glance, the interfaces look the same to me...
Looks like we'll need to wrap pkging.Pkger
... I'm not picky about what type Pkger struct
looks like as long as the fields aren't exported.
Thinking about this a bit more I'm honestly wondering if it's worth adding this driver at all since it essentially only wraps
pkging.Pkger
inhttp.FileSystem
. It might make sense to just put it on the user to wrap it themselves and instead usehttpfs.New
directly. What do you think about that?
If you're only going to use migrate
as a Go library, then httpfs.New()
is probably the easiest and quickest route to get started. However, if you want to use DB URIs and the migrate
CLI, you'll need to create a driver using the httpfs.PartialDriver
and register it. You can't use httpfs.New()
for this since doing so will hardcode the driver to the http.FileSystem
specified via httpfs.New()
.
I'd recommend having CLI support to debug and fix any issues you have with your migrations
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.
However, if you want to use DB URIs and the migrate CLI, you'll need to create a driver using the httpfs.PartialDriver and register it. You can't use httpfs.New() for this since doing so will hardcode the driver to the http.FileSystem specified via httpfs.New().
I'm not sure I understand what you mean by hardcoding the driver to the specified file sytem. Could you elaborate?
How would the CLI support a source that is explicitly for embedded data? What would it mean to tell the CLI to read migrations from pkger:///migrations
?
Looking at go_bindata
it doesn't implement CLI support and as you can see I just did the same by leaving source.Driver.Open
unimplemented. Personally I only use migrate
as a library.
That said, consumers of pkger
mostly register resources in a global instance of pkging.Pkger
using pkger.Apply
and then access files using package scoped functions pkger.Open
. This global instance it not, as far as I can tell, accessible. It seems like a good idea to use source.Driver.Open
to return a driver that reads from the global pkging.Pkger
instance. It would allow library users to opt for either WithInstance
and their own instance of pkging.Pkger
or for Open
after registering migrations on the global instance with pkger.Apply
. The pkger
CLI currently only generates code that registers embedded resources in the global instance so it would be nice to allowing access to it.
I've actually already gone ahead ahead and implemented this along with increased test coverage. I'm happy to address any new feedback on that.
On the back of the above to partly answer my own question, pkger:///migrations
will read migrations from the relative directory /migrations
using the package scoped pkger.Open
i.e. using a global instance of pkging.Pkger
. However in the context of the CLI I'm not sure that helps anyone since they can't register migrations on the global instance that exists only in memory during the execution of the CLI, right?
source/pkger/pkger.go
Outdated
} | ||
|
||
// Open implements http.FileSystem. | ||
func (p *Instance) Open(name string) (http.File, error) { |
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.
Open()
should call Init()
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.
Does this still apply after reading the explanation of why there are two structs?
source/pkger/pkger.go
Outdated
|
||
fs = p | ||
|
||
if err := ds.Init(fs, p.Path); err != nil { |
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.
Init()
should be called with an instance of pkging.Pkger
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.
Does this still apply after reading the explanation of why there are two structs?
pkging.Pkger
does not implement http.FileSystem
. Do you mean to call Init
with *Pkger
(previously *Instance
)? It is in fact already doing that. If it's unclear, perhaps I could remove
var fs http.FileSystem
fs = instance
and pass instance
directly to Init
. Would that make it clearer?
source/pkger/pkger.go
Outdated
return f.(http.File), nil | ||
} | ||
|
||
type driver struct { |
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.
You can use one struct by not embedding pkging.Pkger
.
e.g.
type Pkger struct {
httpfs.PartialDriver
pkger pkging.Pkger
}
It would've been nice if
pkging.Pkger
implementedhttp.FileSystem
but it turns out in fact it does not.
Pkger
(previouslyInstance
) wrapspkging.Pkger
and implementshttp.FileSystem
so that it can be passed to the partial driver.
You can pass Pkger.pkger
to Init()
} | ||
|
||
// wrap pkger to implement http.FileSystem. | ||
fs := fsFunc(func(name string) (http.File, error) { |
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 be able to refactor this to return WithInstance(pkger, u.Path)
. Based on the pkgr.Open(), we'd probably have to use here.Current().
This is not a blocker for this PR since the difference in complexity is arugable...
e.g. the setup required to use WithInstance()
may not be worth the duplicated code
Lemme know if you don't think this refactor is worth it and I'll go ahead and merge the PR as is.
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 say go ahead and merge it as is. I'm not 100% sure, but if I change my mind I'll go ahead and contribute a refactor :)
Thanks!
How do I use this PR? I read through |
@A1Liu it depends on how you're using Pkger. Here is one example
Does that help? Let me know if there is anything that needs explaining. |
Thank you! That's exactly what I needed. |
I am using sqlite db, therefore my code structure looks something like this:
But I am getting this error,
Can you please help here? Thanks in advance. |
@hinupurthakur it looks like you are not using modules or have structured your code such that Pkger is unable to locate the module root, as indicated by the error message: This is the structure of the above minimal example with your path applied
created with
Hope that is helpful, but let me know if it still does not make sense. |
I would recommend to use package
|
My requirement is to have a single build file for the project without any dependency. The binary works fine if it is in the project but as soon as I move it somewhere else it shows it's dependency on go.mod |
Okay I will try with embed then |
As go-bindata has been abandoned [1] there are open requests, #116, for
alternative sources with similar functionality. The Buffalo project [2]
created packr and recently pkger [3] was announced [4] with the
intention to supersede packr.
This change adds support for using pkger as a source.
The implementation relies on httpfs.PartialDriver for pretty much all
functionality.
[1] jteeuwen/go-bindata#5
[2] https://gobuffalo.io/
[3] https://github.com/markbates/pkger
[4] https://blog.gobuffalo.io/introducing-pkger-static-file-embedding-in-go-1ce76dc79c65