Skip to content
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

Smart HTTP Git transport & partial clones (#291) #332

Merged
merged 1 commit into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/soft/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ func init() {
func main() {
logger := NewDefaultLogger()

// Set global logger
log.SetDefault(logger)

// Set the max number of processes to the number of CPUs
// This is useful when running soft serve in a container
if _, err := maxprocs.Set(maxprocs.Logger(logger.Debugf)); err != nil {
Expand Down
7 changes: 0 additions & 7 deletions git/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,6 @@ func (r *Repository) CommitsByPage(ref *Reference, page, size int) (Commits, err
return commits, nil
}

// UpdateServerInfo updates the repository server info.
func (r *Repository) UpdateServerInfo() error {
cmd := git.NewCommand("update-server-info")
_, err := cmd.RunInDir(r.Path)
return err
}

// Config returns the config value for the given key.
func (r *Repository) Config(key string, opts ...ConfigOptions) (string, error) {
dir, err := gitDir(r.Repository)
Expand Down
18 changes: 18 additions & 0 deletions git/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package git

import (
"context"

"github.com/gogs/git-module"
)

// UpdateServerInfo updates the server info file for the given repo path.
func UpdateServerInfo(ctx context.Context, path string) error {
if !isGitDir(path) {
return ErrNotAGitRepository
}

Check warning on line 13 in git/server.go

View check run for this annotation

Codecov / codecov/patch

git/server.go#L10-L13

Added lines #L10 - L13 were not covered by tests

cmd := git.NewCommand("update-server-info").WithContext(ctx).WithTimeout(-1)
_, err := cmd.RunInDir(path)
return err

Check warning on line 17 in git/server.go

View check run for this annotation

Codecov / codecov/patch

git/server.go#L15-L17

Added lines #L15 - L17 were not covered by tests
}
23 changes: 23 additions & 0 deletions git/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package git

import (
"os"
"path/filepath"

"github.com/gobwas/glob"
Expand Down Expand Up @@ -49,3 +50,25 @@
}
return "", "", ErrFileNotFound
}

// Returns true if path is a directory containing an `objects` directory and a
// `HEAD` file.
func isGitDir(path string) bool {
stat, err := os.Stat(filepath.Join(path, "objects"))
if err != nil {
return false
}
if !stat.IsDir() {
return false
}

Check warning on line 63 in git/utils.go

View check run for this annotation

Codecov / codecov/patch

git/utils.go#L56-L63

Added lines #L56 - L63 were not covered by tests

stat, err = os.Stat(filepath.Join(path, "HEAD"))
if err != nil {
return false
}
if stat.IsDir() {
return false
}

Check warning on line 71 in git/utils.go

View check run for this annotation

Codecov / codecov/patch

git/utils.go#L65-L71

Added lines #L65 - L71 were not covered by tests

return true

Check warning on line 73 in git/utils.go

View check run for this annotation

Codecov / codecov/patch

git/utils.go#L73

Added line #L73 was not covered by tests
}
4 changes: 4 additions & 0 deletions internal/log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@

if debug, _ := strconv.ParseBool(os.Getenv("SOFT_SERVE_DEBUG")); debug {
logger.SetLevel(log.DebugLevel)

if verbose, _ := strconv.ParseBool(os.Getenv("SOFT_SERVE_VERBOSE")); verbose {
logger.SetReportCaller(true)
}

Check warning on line 38 in internal/log/log.go

View check run for this annotation

Codecov / codecov/patch

internal/log/log.go#L35-L38

Added lines #L35 - L38 were not covered by tests
}

logger.SetTimeFormat(cfg.Log.TimeFormat)
Expand Down
24 changes: 0 additions & 24 deletions server/backend/sqlite/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,6 @@ func (d *SqliteBackend) PostUpdate(stdout io.Writer, stderr io.Writer, repo stri

var wg sync.WaitGroup

// Update server info
wg.Add(1)
go func() {
defer wg.Done()
if err := updateServerInfo(d, repo); err != nil {
d.logger.Error("error updating server-info", "repo", repo, "err", err)
return
}
}()

// Populate last-modified file.
wg.Add(1)
go func() {
Expand All @@ -59,20 +49,6 @@ func (d *SqliteBackend) PostUpdate(stdout io.Writer, stderr io.Writer, repo stri
wg.Wait()
}

func updateServerInfo(d *SqliteBackend, repo string) error {
rr, err := d.Repository(repo)
if err != nil {
return err
}

r, err := rr.Open()
if err != nil {
return err
}

return r.UpdateServerInfo()
}

func populateLastModified(d *SqliteBackend, repo string) error {
var rr *Repo
_rr, err := d.Repository(repo)
Expand Down
7 changes: 1 addition & 6 deletions server/backend/sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,12 @@ func (d *SqliteBackend) CreateRepository(name string, opts backend.RepositoryOpt
return err
}

rr, err := git.Init(rp, true)
_, err := git.Init(rp, true)
if err != nil {
d.logger.Debug("failed to create repository", "err", err)
return err
}

if err := rr.UpdateServerInfo(); err != nil {
d.logger.Debug("failed to update server info", "err", err)
return err
}

return nil
}); err != nil {
d.logger.Debug("failed to create repository in database", "err", err)
Expand Down
34 changes: 34 additions & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,40 @@
Backend backend.Backend `yaml:"-"`
}

// Environ returns the config as a list of environment variables.
func (c *Config) Environ() []string {
envs := []string{}
if c == nil {
return envs
}

Check warning on line 122 in server/config/config.go

View check run for this annotation

Codecov / codecov/patch

server/config/config.go#L121-L122

Added lines #L121 - L122 were not covered by tests

// TODO: do this dynamically
envs = append(envs, []string{
fmt.Sprintf("SOFT_SERVE_NAME=%s", c.Name),
fmt.Sprintf("SOFT_SERVE_DATA_PATH=%s", c.DataPath),
fmt.Sprintf("SOFT_SERVE_INITIAL_ADMIN_KEYS=%s", strings.Join(c.InitialAdminKeys, "\n")),
fmt.Sprintf("SOFT_SERVE_SSH_LISTEN_ADDR=%s", c.SSH.ListenAddr),
fmt.Sprintf("SOFT_SERVE_SSH_PUBLIC_URL=%s", c.SSH.PublicURL),
fmt.Sprintf("SOFT_SERVE_SSH_KEY_PATH=%s", c.SSH.KeyPath),
fmt.Sprintf("SOFT_SERVE_SSH_CLIENT_KEY_PATH=%s", c.SSH.ClientKeyPath),
fmt.Sprintf("SOFT_SERVE_SSH_MAX_TIMEOUT=%d", c.SSH.MaxTimeout),
fmt.Sprintf("SOFT_SERVE_SSH_IDLE_TIMEOUT=%d", c.SSH.IdleTimeout),
fmt.Sprintf("SOFT_SERVE_GIT_LISTEN_ADDR=%s", c.Git.ListenAddr),
fmt.Sprintf("SOFT_SERVE_GIT_MAX_TIMEOUT=%d", c.Git.MaxTimeout),
fmt.Sprintf("SOFT_SERVE_GIT_IDLE_TIMEOUT=%d", c.Git.IdleTimeout),
fmt.Sprintf("SOFT_SERVE_GIT_MAX_CONNECTIONS=%d", c.Git.MaxConnections),
fmt.Sprintf("SOFT_SERVE_HTTP_LISTEN_ADDR=%s", c.HTTP.ListenAddr),
fmt.Sprintf("SOFT_SERVE_HTTP_TLS_KEY_PATH=%s", c.HTTP.TLSKeyPath),
fmt.Sprintf("SOFT_SERVE_HTTP_TLS_CERT_PATH=%s", c.HTTP.TLSCertPath),
fmt.Sprintf("SOFT_SERVE_HTTP_PUBLIC_URL=%s", c.HTTP.PublicURL),
fmt.Sprintf("SOFT_SERVE_STATS_LISTEN_ADDR=%s", c.Stats.ListenAddr),
fmt.Sprintf("SOFT_SERVE_LOG_FORMAT=%s", c.Log.Format),
fmt.Sprintf("SOFT_SERVE_LOG_TIME_FORMAT=%s", c.Log.TimeFormat),
}...)

return envs
}

func parseConfig(path string) (*Config, error) {
dataPath := filepath.Dir(path)
cfg := &Config{
Expand Down
105 changes: 105 additions & 0 deletions server/daemon/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package daemon

import (
"context"
"errors"
"net"
"sync"
"time"
)

// connections is a synchronizes access to to a net.Conn pool.
type connections struct {
m map[net.Conn]struct{}
mu sync.Mutex
}

func (m *connections) Add(c net.Conn) {
m.mu.Lock()
defer m.mu.Unlock()
m.m[c] = struct{}{}
}

func (m *connections) Close(c net.Conn) error {
m.mu.Lock()
defer m.mu.Unlock()
err := c.Close()
delete(m.m, c)
return err
}

func (m *connections) Size() int {
m.mu.Lock()
defer m.mu.Unlock()
return len(m.m)
}

func (m *connections) CloseAll() error {
m.mu.Lock()
defer m.mu.Unlock()
var err error
for c := range m.m {
err = errors.Join(err, c.Close())
delete(m.m, c)
}

Check warning on line 44 in server/daemon/conn.go

View check run for this annotation

Codecov / codecov/patch

server/daemon/conn.go#L37-L44

Added lines #L37 - L44 were not covered by tests

return err

Check warning on line 46 in server/daemon/conn.go

View check run for this annotation

Codecov / codecov/patch

server/daemon/conn.go#L46

Added line #L46 was not covered by tests
}

// serverConn is a wrapper around a net.Conn that closes the connection when
// the one of the timeouts is reached.
type serverConn struct {
net.Conn

initTimeout time.Duration
idleTimeout time.Duration
maxDeadline time.Time
closeCanceler context.CancelFunc
}

var _ net.Conn = (*serverConn)(nil)

func (c *serverConn) Write(p []byte) (n int, err error) {
c.updateDeadline()
n, err = c.Conn.Write(p)
if _, isNetErr := err.(net.Error); isNetErr && c.closeCanceler != nil {
c.closeCanceler()
}
return
}

func (c *serverConn) Read(b []byte) (n int, err error) {
c.updateDeadline()
n, err = c.Conn.Read(b)
if _, isNetErr := err.(net.Error); isNetErr && c.closeCanceler != nil {
c.closeCanceler()
}
return
}

func (c *serverConn) Close() (err error) {
err = c.Conn.Close()
if c.closeCanceler != nil {
c.closeCanceler()
}
return
}

func (c *serverConn) updateDeadline() {
switch {
case c.initTimeout > 0:
initTimeout := time.Now().Add(c.initTimeout)
c.initTimeout = 0
if initTimeout.Unix() < c.maxDeadline.Unix() || c.maxDeadline.IsZero() {
c.Conn.SetDeadline(initTimeout)
return
}

Check warning on line 96 in server/daemon/conn.go

View check run for this annotation

Codecov / codecov/patch

server/daemon/conn.go#L90-L96

Added lines #L90 - L96 were not covered by tests
case c.idleTimeout > 0:
idleDeadline := time.Now().Add(c.idleTimeout)
if idleDeadline.Unix() < c.maxDeadline.Unix() || c.maxDeadline.IsZero() {
c.Conn.SetDeadline(idleDeadline)
return
}
}
c.Conn.SetDeadline(c.maxDeadline)

Check warning on line 104 in server/daemon/conn.go

View check run for this annotation

Codecov / codecov/patch

server/daemon/conn.go#L104

Added line #L104 was not covered by tests
}
Loading
Loading