summaryrefslogtreecommitdiffstats
path: root/module.go
blob: e83641dcb4f96f84688816c9cb89f48f0957ed8e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package caddyfsgit

import (
	"io/fs"
	"errors"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
	"go.uber.org/zap"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/thediveo/gitrepofs"
)

func init() {
	caddy.RegisterModule(FS{})
}

// Interface guards
var (
	_ fs.FS                 = (*FS)(nil)
	_ fs.StatFS             = (*FS)(nil)
	_ caddyfile.Unmarshaler = (*FS)(nil)
)

// FS provides a view into a specific git tree.
type FS struct {
	fs.FS              `json:"-"`
	Repository  string `json:"repository,omitempty"`
	Revision    string `json:"revision,omitempty"`
	revCommit   plumbing.Hash
	repo        *git.Repository
	logger      *zap.Logger
}

// CaddyModule returns the Caddy module information.
func (FS) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "caddy.fs.git",
		New: func() caddy.Module { return new(FS) },
	}
}

func (fs *FS) Provision(ctx caddy.Context) error {
	fs.logger = ctx.Logger(fs)
	if fs.Repository == "" {
		fs.logger.Error("Repository is unset")
		return errors.New("repository must be set")
	}
    repo, err := git.PlainOpen(fs.Repository)
    if err != nil {
		fs.logger.Error("failed to open git repository", zap.Error(err),
			zap.String("path", fs.Repository))
        return err
    }
	if fs.Revision == "" {
		fs.logger.Info("Git Revision unset, defaulting to HEAD")
		fs.Revision = "HEAD"
	}
	fs.repo = repo
    return nil
}

func (fs *FS) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
	if !d.Next() { // consume start of block
		return d.ArgErr()
	}
	if d.NextArg() { // Optional "fs git <repo>" form
		fs.Repository = d.Val()
	} else {
		// Form: fs git {
		//   repo[sitory] <path>
		//   rev[ision] <rev> (optional)
		// }
		for nesting := d.Nesting(); d.NextBlock(nesting); {
			switch d.Val() {
			case "repository", "repo":
				if !d.Args(&fs.Repository) {
					return d.ArgErr()
				}
			case "revision", "rev":
				if !d.Args(&fs.Revision) {
					return d.ArgErr()
				}
			default:
				return d.Errf("'%s' not a valid caddy.fs.git option", d.Val())
			}
		}
	}
	return nil
}

func (gfs *FS) RepoFS() (fs.FS, error) {
    hash, err := gfs.repo.ResolveRevision(plumbing.Revision(gfs.Revision))
    if err != nil {
		gfs.logger.Error("failed to resolve revision", zap.Error(err))
        return nil, err
    }
	if *hash ==gfs.revCommit {
		return gfs.FS, nil
	}
    commit, err := gfs.repo.CommitObject(*hash)
    if err != nil {
		gfs.logger.Error("failed to get commit object", zap.Error(err))
        return nil, err
    }
    tree, err := commit.Tree()
    if err != nil {
        gfs.logger.Error("failed to get tree object", zap.Error(err))
		return nil, err
    }
	gfs.revCommit = *hash
	gfs.FS = gitrepofs.New(gfs.repo, tree, commit.Author.When)
	return gfs.FS, nil
}

func (gfs *FS) Open(name string) (fs.File, error) {
	repofs, err := gfs.RepoFS()
	if err != nil {
		return nil, err
	}
	return repofs.Open(name)
}

// To implement StatFS
func (gfs *FS) Stat(name string) (fs.FileInfo, error) {
	repofs, err := gfs.RepoFS()
	if err != nil {
		return nil, err
	}
	file, err := repofs.Open(name)
	if err != nil {
		return nil, err
	}
	return file.Stat()
}