Skip to content

Commit 5bcc8b5

Browse files
author
Esteban Garcia
committed
initial commit
0 parents  commit 5bcc8b5

30 files changed

+2611
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lfsproxy.yaml

.lint/config.yml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
linters:
2+
enable:
3+
# Enable specific linter
4+
# https://golangci-lint.run/usage/linters
5+
- asciicheck
6+
- durationcheck
7+
- errorlint
8+
- gocyclo
9+
- gofmt
10+
- goimports
11+
- gosec
12+
- maintidx
13+
- wastedassign
14+
- exportloopref
15+
issues:
16+
exclude-rules:
17+
# Exclude some linters from running on tests files.
18+
- path: _test\.go
19+
linters:
20+
- gocyclo
21+
- errcheck
22+
- gosec
23+
- maintidx
24+
- errorlint

Dockerfile

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM golang:1.20 AS build
2+
3+
WORKDIR /app
4+
5+
COPY go.* ./
6+
RUN go mod download
7+
8+
COPY . ./
9+
10+
ENV CGO_ENABLED=0
11+
12+
RUN go build -v -o lfsproxy ./cmd/server.go
13+
14+
FROM alpine:3.17
15+
16+
WORKDIR /app
17+
18+
RUN apk add --no-cache libc6-compat gcompat
19+
20+
COPY --from=build /app/lfsproxy /app/lfsproxy
21+
CMD ["/app/lfsproxy"]

LICENSE

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
MIT License
3+
4+
Copyright (c) 2022 Vela Games Ltd
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.

README.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# LFS Proxy
2+
3+
This service is a pull-through S3 cache for [Git LFS](https://git-lfs.com/) that caches Upstream LFS objects on S3 to reduce bandwidth costs on Hosted LFS, such as GitHub LFS.
4+
5+
These cached objects are served directly from S3 using Presigned requests for maximum performance.
6+
7+
Requests are cached in-memory using [bigcache](https://github.com/allegro/bigcache) to reduce the amount of HTTP calls to S3
8+
9+
## Installing the LFS Proxy
10+
11+
We are providing a [Helm Chart](https://github.com/vela-games/lfsproxy/tree/main/install/helm/lfsproxy) and an example [terrafom module](https://github.com/vela-games/lfsproxy/tree/main/install/terraform/lfsproxy) for deploying the service
12+
13+
You'll need to make sure the service account deployed by the chart uses an IAM Role with access to S3 if you are running it on Kubernetes.
14+
15+
We currently don't have any public repositories for the Docker Image or the Helm chart, but is something we are looking into.
16+
17+
## Configurations
18+
19+
All configurations are loaded from environment variables using [envconfig](https://github.com/kelseyhightower/envconfig).
20+
21+
| Configuration Name | Environment Variable | Default Value | Description |
22+
|--------------------------------|--------------------------------------|--------------------------------------------------|---------------------------------------------------------------------------------------------------|
23+
| DebugMode | APP_DEBUG_MODE | false | Enable gin-gonic debug mode |
24+
| UpstreamBaseURL | APP_UPSTREAM_BASE_URL | | The LFS Git Repository base url (Example: https://github.com/vela-games/example.git/info/lfs/) |
25+
| S3Bucket | APP_S3_BUCKET | | S3 Bucket Name |
26+
| S3UseAccelerate | APP_S3_USE_ACCELERATE | false | If S3 Accelerate URLs should be returned |
27+
| S3PresignEnabled | APP_S3_PRESIGN_ENABLED | true | If S3 Presign URLs should be used |
28+
| S3PresignExpiration | APP_S3_PRESIGN_EXPIRATION | 24h | Presign Expiration |
29+
| CacheEviction | APP_CACHE_EVICTION | 23h | When to evict cached requests from memory |
30+
| EnablePrometheusExporter | APP_ENABLE_PROMETHEUS_EXPORTER | false | Enable Prometheus exporter endpoint (/metrics) |

cache/cache.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package cache
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/allegro/bigcache/v3"
8+
)
9+
10+
type Cache interface {
11+
Get(key string) ([]byte, error)
12+
Set(key string, entry []byte) error
13+
Delete(key string) error
14+
}
15+
16+
func NewCache(ctx context.Context, cacheEviction time.Duration) (Cache, error) {
17+
cache, err := bigcache.New(ctx, bigcache.DefaultConfig(cacheEviction))
18+
if err != nil {
19+
return nil, err
20+
}
21+
22+
return cache, nil
23+
}

cmd/server.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/vela-games/lfsproxy/config"
8+
"github.com/vela-games/lfsproxy/router"
9+
10+
"context"
11+
"os"
12+
"os/signal"
13+
"syscall"
14+
)
15+
16+
const PORT = 8080
17+
18+
// NewSigKillContext returns a Context that cancels when os.Interrupt or os.Kill is received
19+
func NewSigKillContext() context.Context {
20+
ctx, cancel := context.WithCancel(context.Background())
21+
c := make(chan os.Signal, 2)
22+
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
23+
go func() {
24+
<-c
25+
cancel()
26+
}()
27+
28+
return ctx
29+
}
30+
31+
func main() {
32+
ctx := NewSigKillContext()
33+
cfg, err := config.GetConfig()
34+
if err != nil {
35+
log.Panicf("error getting configuration: %v", err)
36+
}
37+
38+
router := router.NewRouter()
39+
err = router.InitRoutes(ctx, cfg)
40+
if err != nil {
41+
panic(err)
42+
}
43+
44+
err = router.Run(ctx, fmt.Sprintf(":%v", PORT))
45+
if err != nil {
46+
panic(err)
47+
}
48+
49+
}

config/config.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package config
2+
3+
import (
4+
"time"
5+
6+
"github.com/kelseyhightower/envconfig"
7+
)
8+
9+
type Config struct {
10+
DebugMode bool `split_words:"true" default:"false"`
11+
UpstreamBaseURL string `split_words:"true" required:"true"`
12+
CacheEviction time.Duration `split_words:"true" default:"23h"`
13+
S3Bucket string `split_words:"true" required:"true"`
14+
S3UseAccelerate bool `split_words:"true" default:"false"`
15+
S3PresignEnabled bool `split_words:"true" default:"true"`
16+
S3PresignExpiration time.Duration `split_words:"true" default:"24h"`
17+
EnablePrometheusExporter bool `split_words:"true" default:"false"`
18+
}
19+
20+
func GetConfig() (*Config, error) {
21+
var proxyConfiguration Config
22+
23+
err := envconfig.Process("app", &proxyConfiguration)
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
return &proxyConfiguration, nil
29+
}

exporter/collector.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package exporter
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
"github.com/go-kit/kit/metrics"
6+
"github.com/go-kit/kit/metrics/prometheus"
7+
stdprometheus "github.com/prometheus/client_golang/prometheus"
8+
"github.com/prometheus/client_golang/prometheus/promhttp"
9+
)
10+
11+
type LFSProxyCollector struct {
12+
CacheHits metrics.Counter
13+
CacheMiss metrics.Counter
14+
S3Hits metrics.Counter
15+
S3Miss metrics.Counter
16+
}
17+
18+
func NewCollector() *LFSProxyCollector {
19+
return &LFSProxyCollector{
20+
CacheHits: prometheus.NewCounterFrom(stdprometheus.CounterOpts{
21+
Namespace: "lfsproxy",
22+
Name: "cache_hit",
23+
Help: "In-memory Cache Hits",
24+
}, []string{}),
25+
CacheMiss: prometheus.NewCounterFrom(stdprometheus.CounterOpts{
26+
Namespace: "lfsproxy",
27+
Name: "cache_miss",
28+
Help: "In-memory Cache Misses",
29+
}, []string{}),
30+
S3Hits: prometheus.NewCounterFrom(stdprometheus.CounterOpts{
31+
Namespace: "lfsproxy",
32+
Name: "s3_hit",
33+
Help: "S3 Cache Hits",
34+
}, []string{}),
35+
S3Miss: prometheus.NewCounterFrom(stdprometheus.CounterOpts{
36+
Namespace: "lfsproxy",
37+
Name: "s3_miss",
38+
Help: "S3 Cache Misses",
39+
}, []string{}),
40+
}
41+
}
42+
43+
func PrometheusHandler() gin.HandlerFunc {
44+
h := promhttp.Handler()
45+
46+
return func(c *gin.Context) {
47+
h.ServeHTTP(c.Writer, c.Request)
48+
}
49+
}

go.mod

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
module github.com/vela-games/lfsproxy
2+
3+
go 1.20
4+
5+
require (
6+
github.com/allegro/bigcache/v3 v3.1.0
7+
github.com/aws/aws-sdk-go v1.44.236
8+
github.com/gin-contrib/cors v1.4.0
9+
github.com/gin-contrib/gzip v0.0.6
10+
github.com/gin-gonic/gin v1.9.1
11+
github.com/go-kit/kit v0.12.0
12+
github.com/jarcoal/httpmock v1.3.1
13+
github.com/kelseyhightower/envconfig v1.4.0
14+
github.com/prometheus/client_golang v1.14.0
15+
github.com/spf13/viper v1.15.0
16+
github.com/stretchr/testify v1.8.3
17+
)
18+
19+
require (
20+
github.com/beorn7/perks v1.0.1 // indirect
21+
github.com/bytedance/sonic v1.9.1 // indirect
22+
github.com/cespare/xxhash/v2 v2.1.2 // indirect
23+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
24+
github.com/davecgh/go-spew v1.1.1 // indirect
25+
github.com/fsnotify/fsnotify v1.6.0 // indirect
26+
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
27+
github.com/gin-contrib/sse v0.1.0 // indirect
28+
github.com/go-playground/locales v0.14.1 // indirect
29+
github.com/go-playground/universal-translator v0.18.1 // indirect
30+
github.com/go-playground/validator/v10 v10.14.0 // indirect
31+
github.com/goccy/go-json v0.10.2 // indirect
32+
github.com/golang/protobuf v1.5.2 // indirect
33+
github.com/hashicorp/hcl v1.0.0 // indirect
34+
github.com/jmespath/go-jmespath v0.4.0 // indirect
35+
github.com/json-iterator/go v1.1.12 // indirect
36+
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
37+
github.com/leodido/go-urn v1.2.4 // indirect
38+
github.com/magiconair/properties v1.8.7 // indirect
39+
github.com/mattn/go-isatty v0.0.19 // indirect
40+
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
41+
github.com/mitchellh/mapstructure v1.5.0 // indirect
42+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
43+
github.com/modern-go/reflect2 v1.0.2 // indirect
44+
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
45+
github.com/pmezard/go-difflib v1.0.0 // indirect
46+
github.com/prometheus/client_model v0.3.0 // indirect
47+
github.com/prometheus/common v0.37.0 // indirect
48+
github.com/prometheus/procfs v0.8.0 // indirect
49+
github.com/spf13/afero v1.9.3 // indirect
50+
github.com/spf13/cast v1.5.0 // indirect
51+
github.com/spf13/jwalterweatherman v1.1.0 // indirect
52+
github.com/spf13/pflag v1.0.5 // indirect
53+
github.com/subosito/gotenv v1.4.2 // indirect
54+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
55+
github.com/ugorji/go/codec v1.2.11 // indirect
56+
golang.org/x/arch v0.3.0 // indirect
57+
golang.org/x/crypto v0.9.0 // indirect
58+
golang.org/x/net v0.10.0 // indirect
59+
golang.org/x/sys v0.8.0 // indirect
60+
golang.org/x/text v0.9.0 // indirect
61+
google.golang.org/protobuf v1.30.0 // indirect
62+
gopkg.in/ini.v1 v1.67.0 // indirect
63+
gopkg.in/yaml.v3 v3.0.1 // indirect
64+
)

0 commit comments

Comments
 (0)