github.com/posener/gitfs@v1.2.2-0.20200410105819-ea4e48d73ab9/gitfs.go (about) 1 // Package gitfs is a complete solution for static files in Go code. 2 // 3 // When Go code uses non-Go files, they are not packaged into the binary. 4 // The common approach to the problem, as implemented by 5 // (go-bindata) https://github.com/kevinburke/go-bindata 6 // is to convert all the required static files into Go code, which 7 // eventually compiled into the binary. 8 // 9 // This library takes a different approach, in which the static files are not 10 // required to be "binary-packed", and even no required to be in the same repository 11 // as the Go code. This package enables loading static content from a remote 12 // git repository, or packing it to the binary if desired or loaded 13 // from local path for development process. The transition from remote repository 14 // to binary packed content, to local content is completely smooth. 15 // 16 // *The API is simple and minimalistic*. The `New` method returns a (sub)tree 17 // of a Git repository, represented by the standard `http.FileSystem` interface. 18 // This object enables anything that is possible to do with a regular filesystem, 19 // such as opening a file or listing a directory. 20 // Additionally, the ./fsutil package provides enhancements over the `http.FileSystem` 21 // object (They can work with any object that implements the interface) such 22 // as loading Go templates in the standard way, walking over the filesystem, 23 // and applying glob patterns on a filesystem. 24 // 25 // Supported features: 26 // 27 // * Loading of specific version/tag/branch. 28 // 29 // * For debug purposes, the files can be loaded from local path instead of the 30 // remote repository. 31 // 32 // * Files are loaded lazily by default or they can be preloaded if required. 33 // 34 // * Files can be packed to the Go binary using a command line tool. 35 // 36 // * This project is using the standard `http.FileSystem` interface. 37 // 38 // * In ./fsutil there are some general useful tools around the 39 // `http.FileSystem` interace. 40 // 41 // Usage 42 // 43 // To create a filesystem using the `New` function, provide the Git 44 // project with the pattern: `github.com/<owner>/<repo>(/<path>)?(@<ref>)?`. 45 // If no `path` is specified, the root of the project will be used. 46 // `ref` can be any git branch using `heads/<branch name>` or any 47 // git tag using `tags/<tag>`. If the tag is of Semver format, the `tags/` 48 // prefix is not required. If no `ref` is specified, the default branch will 49 // be used. 50 // 51 // In the following example, the repository `github.com/x/y` at tag v1.2.3 52 // and internal path "static" is loaded: 53 // 54 // fs, err := gitfs.New(ctx, "github.com/x/y/static@v1.2.3") 55 // 56 // The variable `fs` implements the `http.FileSystem` interface. 57 // Reading a file from the repository can be done using the `Open` method. 58 // This function accepts a path, relative to the root of the defined 59 // filesystem. 60 // 61 // f, err := fs.Open("index.html") 62 // 63 // The `fs` variable can be used in anything that accept the standard interface. 64 // For example, it can be used for serving static content using the standard 65 // library: 66 // 67 // http.Handle("/", http.FileServer(fs)) 68 // 69 // Private Repositories 70 // 71 // When used with private github repository, the Github API calls should be 72 // instrumented with the appropriate credentials. The credentials can be 73 // passed by providing an HTTP client. 74 // 75 // For example, to use a Github Token from environnement variable `GITHUB_TOKEN`: 76 // 77 // token := os.Getenv("GITHUB_TOKEN") 78 // client := oauth2.NewClient( 79 // context.Background(), 80 // oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})) 81 // fs, err := gitfs.New(ctx, "github.com/x/y", gitfs.OptClient(client)) 82 // 83 // Development 84 // 85 // For quick development workflows, it is easier and faster to use local static 86 // content and not remote content that was pushed to a remote repository. 87 // This is enabled by the `OptLocal` option. To use this option only in 88 // local development and not in production system, it can be used as follow: 89 // 90 // local := os.Getenv("LOCAL_DEBUG") 91 // fs, err := gitfs.New(ctx, "github.com/x/y", gitfs.OptLocal(local)) 92 // 93 // In this example, we stored the value for `OptLocal` in an environment 94 // variable. As a result, when running the program with `LOCAL_DEBUG=.` 95 // local files will be used, while running without it will result in using 96 // the remote files. (the value of the environment variable should point 97 // to any directory within the github project). 98 // 99 // Binary Packing 100 // 101 // Using gitfs does not mean that files are required to be remotely fetched. 102 // When binary packing of the files is needed, a command line tool can pack 103 // them for you. 104 // 105 // To get the tool run: `go get github.com/posener/gitfs/cmd/gitfs`. 106 // 107 // Running the tool is by `gitfs <patterns>`. This generates a `gitfs.go` 108 // file in the current directory that contains all the used filesystems' data. 109 // This will cause all `gitfs.New` calls to automatically use the packed data, 110 // insted of fetching the data on runtime. 111 // 112 // By default, a test will also be generated with the code. This test fails 113 // when the local files are modified without updating the binary content. 114 // 115 // Use binary-packing with `go generate`: To generate all filesystems used 116 // by a project add `//go:generate gitfs ./...` in the root of the project. 117 // To generate only a specific filesystem add `//go:generate gitfs $GOFILE` in 118 // the file it is being used. 119 // 120 // An interesting anecdote is that gitfs command is using itself for generating 121 // its own templates. 122 // 123 // Excluding files 124 // 125 // Files exclusion can be done by including only specific files using a glob 126 // pattern with `OptGlob` option, using the Glob options. This will affect 127 // both local loading of files, remote loading and binary packing (may 128 // reduce binary size). For example: 129 // 130 // fs, err := gitfs.New(ctx, 131 // "github.com/x/y/templates", 132 // gitfs.OptGlob("*.gotmpl", "*/*.gotmpl")) 133 package gitfs 134 135 import ( 136 "context" 137 "net/http" 138 139 "github.com/pkg/errors" 140 "github.com/posener/gitfs/fsutil" 141 "github.com/posener/gitfs/internal/binfs" 142 "github.com/posener/gitfs/internal/githubfs" 143 "github.com/posener/gitfs/internal/localfs" 144 "github.com/posener/gitfs/internal/log" 145 ) 146 147 // OptClient sets up an HTTP client to perform request to the remote repository. 148 // This client can be used for authorization credentials. 149 func OptClient(client *http.Client) option { 150 return func(c *config) { 151 c.client = client 152 } 153 } 154 155 // OptLocal result in looking for local git repository before accessing remote 156 // repository. The given path should be contained in a git repository which 157 // has a remote URL that matches the requested project. 158 func OptLocal(path string) option { 159 return func(c *config) { 160 c.localPath = path 161 } 162 } 163 164 // OptPrefetch sets prefetching all files in the filesystem when it is initially 165 // loaded. 166 func OptPrefetch(prefetch bool) option { 167 return func(c *config) { 168 c.prefetch = prefetch 169 } 170 } 171 172 // OptGlob define glob patterns for which only matching files and directories 173 // will be included in the filesystem. 174 func OptGlob(patterns ...string) option { 175 return func(c *config) { 176 c.patterns = patterns 177 } 178 } 179 180 // New returns a new git filesystem for the given project. 181 // 182 // Github: 183 // If the given project is a github project (of the form github.com/<owner>/<repo>(@<ref>)?(#<path>)? ), 184 // the returned filesystem will be fetching files from the given project. 185 // ref is optional and can be any github ref: 186 // * `heads/<branch name>` for a branch. 187 // * `tags/<tag>` for releases or git tags. 188 // * `<version>` for Semver compatible releases (e.g. v1.2.3). 189 // If no ref is set, the default branch will be used. 190 func New(ctx context.Context, project string, opts ...option) (http.FileSystem, error) { 191 var c config 192 for _, opt := range opts { 193 opt(&c) 194 } 195 196 switch { 197 case c.localPath != "": 198 log.Printf("FileSystem %q from local directory", project) 199 fs, err := localfs.New(project, c.localPath) 200 if err != nil { 201 return nil, err 202 } 203 return fsutil.Glob(fs, c.patterns...) 204 case binfs.Match(project): 205 log.Printf("FileSystem %q from binary", project) 206 return binfs.Get(project), nil 207 case githubfs.Match(project): 208 log.Printf("FileSystem %q from remote Github repository", project) 209 return githubfs.New(ctx, c.client, project, c.prefetch, c.patterns) 210 default: 211 return nil, errors.Errorf("project %q not supported", project) 212 } 213 } 214 215 // WithContext applies context to an http.File if it implements the 216 // contexter interface. 217 // 218 // Usage example: 219 // 220 // f, err := fs.Open("file") 221 // // Handle err... 222 // f = gitfs.WithContext(f, ctx) 223 // _, err = f.Read(...) 224 func WithContext(f http.File, ctx context.Context) http.File { 225 fCtx, ok := f.(contexter) 226 if !ok { 227 return f 228 } 229 return fCtx.WithContext(ctx) 230 } 231 232 // SetLogger sets informative logging for gitfs. If nil, no logging 233 // will be done. 234 func SetLogger(logger log.Logger) { 235 log.Log = logger 236 } 237 238 type config struct { 239 client *http.Client 240 localPath string 241 prefetch bool 242 patterns []string 243 } 244 245 type option func(*config) 246 247 type contexter interface { 248 WithContext(ctx context.Context) http.File 249 }