Browse Source

Initial version

master
Chris Smith 5 years ago
commit
fdc9ddf697
7 changed files with 188 additions and 0 deletions
  1. 1
    0
      .gitignore
  2. 12
    0
      Dockerfile
  3. 21
    0
      LICENCE
  4. 37
    0
      README.md
  5. 8
    0
      go.mod
  6. 4
    0
      go.sum
  7. 105
    0
      main.go

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
1
+.idea

+ 12
- 0
Dockerfile View File

@@ -0,0 +1,12 @@
1
+FROM golang:1.12 AS build
2
+
3
+WORKDIR /go/src/app
4
+
5
+COPY . .
6
+RUN CGO_ENABLED=0 GO111MODULE=on go install .
7
+
8
+FROM scratch
9
+COPY --from=build /go/bin/github-release-redirector /github-release-redirector
10
+COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
11
+
12
+ENTRYPOINT ["/github-release-redirector"]

+ 21
- 0
LICENCE View File

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

+ 37
- 0
README.md View File

@@ -0,0 +1,37 @@
1
+# github-release-redirector
2
+
3
+Small go application that listens for incoming HTTP requests and redirects
4
+to assets from the latest release published to a GitHub repository.
5
+
6
+## Usage
7
+
8
+```console
9
+$ go install .
10
+$ github-release-redirector -port 1234 -repo user/repo
11
+```
12
+
13
+or using docker:
14
+
15
+```console
16
+$ docker run -p 8080 csmith/github-release-redirector -repo user/repo
17
+```
18
+
19
+## Notes
20
+
21
+Releases are refreshed at startup and then once an hour.
22
+
23
+Assets are matched on their name, so an asset attached to the latest release
24
+named "myproject-installer-1.2.3.exe" will be available at the URL
25
+`/myproject-installer-1.2.3.exe`.
26
+
27
+Any url not mapped to an asset is responded to with a 404 not found error;
28
+if there is no latest release available all requests will respond with 500
29
+internal server error.
30
+
31
+The service listens only on HTTP, not HTTPS. For production use it should
32
+be placed behind an SSL-terminating proxy such as Traefik, Nginx or HAProxy.
33
+
34
+## Licence
35
+
36
+This software is released under the MIT licence. See the LICENCE file for
37
+full details.

+ 8
- 0
go.mod View File

@@ -0,0 +1,8 @@
1
+module github.com/csmith/github-release-redirector
2
+
3
+go 1.12
4
+
5
+require (
6
+	github.com/google/go-github v17.0.0+incompatible
7
+	github.com/google/go-querystring v1.0.0 // indirect
8
+)

+ 4
- 0
go.sum View File

@@ -0,0 +1,4 @@
1
+github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
2
+github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
3
+github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
4
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=

+ 105
- 0
main.go View File

@@ -0,0 +1,105 @@
1
+package main
2
+
3
+import (
4
+	"context"
5
+	"flag"
6
+	"fmt"
7
+	"github.com/google/go-github/github"
8
+	"log"
9
+	"net/http"
10
+	"os"
11
+	"os/signal"
12
+	"strings"
13
+	"syscall"
14
+	"time"
15
+)
16
+
17
+var (
18
+	owner string
19
+	repo string
20
+	ctx context.Context
21
+	client *github.Client
22
+	release *github.RepositoryRelease
23
+)
24
+
25
+func fetchLatest() {
26
+	latest, _, err := client.Repositories.GetLatestRelease(ctx, owner, repo)
27
+	if err != nil {
28
+		log.Println("Error retrieving latest release", err)
29
+		return
30
+	}
31
+	release = latest
32
+}
33
+
34
+func serve(w http.ResponseWriter, request *http.Request) {
35
+	if release == nil {
36
+		w.WriteHeader(http.StatusInternalServerError)
37
+		_, _ = fmt.Fprint(w, "Unknown release")
38
+		return
39
+	}
40
+
41
+	for _, asset := range release.Assets {
42
+		if "/" + *asset.Name == request.RequestURI {
43
+			w.Header().Add("Location", *asset.BrowserDownloadURL)
44
+			w.WriteHeader(http.StatusTemporaryRedirect)
45
+			return
46
+		}
47
+	}
48
+
49
+	w.WriteHeader(http.StatusNotFound)
50
+	_, _ = fmt.Fprint(w, "Asset not found in release ", *release.Name)
51
+}
52
+
53
+func main() {
54
+	var fullRepo = flag.String("repo", "", "the repository to redirect releases for, in user/repo format")
55
+	var port = flag.Int("port", 8080, "the port to listen on for HTTP requests")
56
+	flag.Parse()
57
+
58
+	if strings.Count(*fullRepo, "/") != 1 {
59
+		log.Println("Repository must be specified in user/repo format")
60
+		return
61
+	}
62
+
63
+	repoParts := strings.Split(*fullRepo, "/")
64
+	owner = repoParts[0]
65
+	repo = repoParts[1]
66
+
67
+	client = github.NewClient(nil)
68
+
69
+	ticker := time.NewTicker(time.Hour)
70
+	go func() {
71
+		fetchLatest()
72
+		for range ticker.C {
73
+			fetchLatest()
74
+		}
75
+	}()
76
+
77
+
78
+	var cancel context.CancelFunc
79
+	ctx, cancel = context.WithCancel(context.Background())
80
+
81
+	c := make(chan os.Signal, 1)
82
+	signal.Notify(c, os.Interrupt)
83
+	signal.Notify(c, syscall.SIGTERM)
84
+
85
+	go func() {
86
+		for sig := range c {
87
+			cancel()
88
+			ticker.Stop()
89
+			log.Printf("Received %s, exiting.", sig.String())
90
+			os.Exit(0)
91
+		}
92
+	}()
93
+
94
+	log.Printf("Listing on :%d", *port)
95
+
96
+	server := &http.Server{
97
+		Addr:    fmt.Sprintf(":%d", *port),
98
+		Handler: http.HandlerFunc(serve),
99
+	}
100
+
101
+	err := server.ListenAndServe()
102
+	if err != nil {
103
+		log.Println("Error listening for requests on port ", port, err)
104
+	}
105
+}

Loading…
Cancel
Save