Skip to content

Commit

Permalink
Merge branch 'main' into aws-sdk-go-v2
Browse files Browse the repository at this point in the history
  • Loading branch information
nesangcode authored Apr 20, 2024
2 parents 8014250 + 359c119 commit 8e3f2f7
Show file tree
Hide file tree
Showing 12 changed files with 332 additions and 45 deletions.
2 changes: 1 addition & 1 deletion cmd/proxy/actions/app_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func addProxyRoutes(
l *log.Logger,
c *config.Config,
) error {
r.HandleFunc("/", proxyHomeHandler)
r.HandleFunc("/", proxyHomeHandler(c))
r.HandleFunc("/healthz", healthHandler)
r.HandleFunc("/readyz", getReadinessHandler(s))
r.HandleFunc("/version", versionHandler)
Expand Down
44 changes: 33 additions & 11 deletions cmd/proxy/actions/app_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http/httptest"
"strings"
"testing"
"text/template"

"github.com/gomods/athens/pkg/build"
"github.com/gomods/athens/pkg/config"
Expand All @@ -21,7 +22,7 @@ type routeTest struct {
method string
path string
body string
test func(t *testing.T, resp *http.Response)
test func(t *testing.T, req *http.Request, resp *http.Response)
}

func TestProxyRoutes(t *testing.T) {
Expand All @@ -40,22 +41,43 @@ func TestProxyRoutes(t *testing.T) {
baseURL := "https://athens.azurefd.net" + c.PathPrefix

testCases := []routeTest{
{"GET", "/", "", func(t *testing.T, resp *http.Response) {
{"GET", "/", "", func(t *testing.T, req *http.Request, resp *http.Response) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, `"Welcome to The Athens Proxy"`, string(body))
tmp, err := template.New("home").Parse(homepage)
assert.NoError(t, err)

var templateData = make(map[string]string)

templateData["Host"] = req.Host

if !strings.HasPrefix(templateData["Host"], "http://") && !strings.HasPrefix(templateData["Host"], "https://") {
if req.TLS != nil {
templateData["Host"] = "https://" + templateData["Host"]
} else {
templateData["Host"] = "http://" + templateData["Host"]
}
}

templateData["NoSumPatterns"] = strings.Join(c.NoSumPatterns, ",")

var expected strings.Builder
err = tmp.ExecuteTemplate(&expected, "home", templateData)
require.NoError(t, err)

assert.Equal(t, expected.String(), string(body))
}},
{"GET", "/badz", "", func(t *testing.T, resp *http.Response) {
{"GET", "/badz", "", func(t *testing.T, req *http.Request, resp *http.Response) {
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}},
{"GET", "/healthz", "", func(t *testing.T, resp *http.Response) {
{"GET", "/healthz", "", func(t *testing.T, req *http.Request, resp *http.Response) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
}},
{"GET", "/readyz", "", func(t *testing.T, resp *http.Response) {
{"GET", "/readyz", "", func(t *testing.T, req *http.Request, resp *http.Response) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
}},
{"GET", "/version", "", func(t *testing.T, resp *http.Response) {
{"GET", "/version", "", func(t *testing.T, req *http.Request, resp *http.Response) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
details := build.Details{}
err := json.NewDecoder(resp.Body).Decode(&details)
Expand All @@ -64,13 +86,13 @@ func TestProxyRoutes(t *testing.T) {
}},

// Default sumdb is sum.golang.org
{"GET", "/sumdb/sum.golang.org/supported", "", func(t *testing.T, resp *http.Response) {
{"GET", "/sumdb/sum.golang.org/supported", "", func(t *testing.T, req *http.Request, resp *http.Response) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
}},
{"GET", "/sumdb/sum.rust-lang.org/supported", "", func(t *testing.T, resp *http.Response) {
{"GET", "/sumdb/sum.rust-lang.org/supported", "", func(t *testing.T, req *http.Request, resp *http.Response) {
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
}},
{"GET", "/sumdb/sum.golang.org/lookup/github.com/gomods/athens", "", func(t *testing.T, resp *http.Response) {
{"GET", "/sumdb/sum.golang.org/lookup/github.com/gomods/athens", "", func(t *testing.T, req *http.Request, resp *http.Response) {
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
}},
}
Expand All @@ -84,7 +106,7 @@ func TestProxyRoutes(t *testing.T) {
t.Run(req.RequestURI, func(t *testing.T) {
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
tc.test(t, w.Result())
tc.test(t, req, w.Result())
})
}

Expand Down
132 changes: 130 additions & 2 deletions cmd/proxy/actions/home.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,137 @@
package actions

import (
"errors"
"html/template"
"net/http"
"os"
"strings"

"github.com/gomods/athens/pkg/config"
"github.com/gomods/athens/pkg/log"
)

func proxyHomeHandler(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`"Welcome to The Athens Proxy"`))
const homepage = `<!DOCTYPE html>
<html>
<head>
<title>Athens</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
pre {
background-color: #f4f4f4;
padding: 5px;
border-radius: 5px;
width: fit-content;
padding: 10px;
}
code {
background-color: #f4f4f4;
padding: 5px;
border-radius: 5px;
}
</style>
</head>
<body>
<h1>Welcome to Athens</h1>
<h2>Configuring your client</h2>
<pre>GOPROXY={{ .Host }},direct</pre>
{{ if .NoSumPatterns }}
<h3>Excluding checksum database</h3>
<p>Use the following GONOSUM environment variable to exclude checksum database:</p>
<pre>GONOSUM={{ .NoSumPatterns }}</pre>
{{ end }}
<h2>How to use the Athens API</h2>
<p>Use the <a href="/catalog">catalog</a> endpoint to get a list of all modules in the proxy</p>
<h3>List of versions</h3>
<p>This endpoint returns a list of versions that Athens knows about for <code>acidburn/htp</code>:</p>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@v/list</pre>
<h3>Version info</h3>
<p>This endpoint returns information about a specific version of a module:</p>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@v/v1.0.0.info</pre>
<p>This returns JSON with information about v1.0.0. It looks like this:
<pre>{
"Name": "v1.0.0",
"Short": "v1.0.0",
"Version": "v1.0.0",
"Time": "1972-07-18T12:34:56Z"
}</pre>
<h3>go.mod file</h3>
<p>This endpoint returns the go.mod file for a specific version of a module:</p>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@v/v1.0.0.mod</pre>
<p>This returns the go.mod file for version v1.0.0. If {{ .Host }}/github.com/acidburn/htp version v1.0.0 has no dependencies, the response body would look like this:</p>
<pre>module github.com/acidburn/htp</pre>
<h3>Module sources</h3>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@v/v1.0.0.zip</pre>
<p>This is what it sounds like — it sends back a zip file with the source code for the module in version v1.0.0.</p>
<h3>Latest</h3>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@latest</pre>
<p>This endpoint returns the latest version of the module. If the version does not exist it should retrieve the hash of latest commit.</p>
</body>
</html>
`

func proxyHomeHandler(c *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
lggr := log.EntryFromContext(r.Context())

templateData := make(map[string]string)

templateContents := homepage

// load the template from the file system if it exists, otherwise revert to default
rawTemplateFileContents, err := os.ReadFile(c.HomeTemplatePath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
// this is some other error, log it and revert to default
lggr.SystemErr(err)
}
} else {
templateContents = string(rawTemplateFileContents)
}

// This should be correct in most cases. If it is not, users can supply their own template
templateData["Host"] = r.Host

// if the host does not have a scheme, add one based on the request
if !strings.HasPrefix(templateData["Host"], "http://") && !strings.HasPrefix(templateData["Host"], "https://") {
if r.TLS != nil {
templateData["Host"] = "https://" + templateData["Host"]
} else {
templateData["Host"] = "http://" + templateData["Host"]
}
}

templateData["NoSumPatterns"] = strings.Join(c.NoSumPatterns, ",")

tmp, err := template.New("home").Parse(templateContents)
if err != nil {
lggr.SystemErr(err)
w.WriteHeader(http.StatusInternalServerError)
}

w.Header().Add("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)

err = tmp.ExecuteTemplate(w, "home", templateData)
if err != nil {
lggr.SystemErr(err)
w.WriteHeader(http.StatusInternalServerError)
}
}
}
4 changes: 4 additions & 0 deletions config.dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ BasicAuthUser = ""
# Env override: BASIC_AUTH_PASS
BasicAuthPass = ""

# A path on disk to a Go HTML template to be used on the homepage
# Env override: ATHENS_HOME_TEMPLATE_PATH
HomeTemplatePath = "/var/lib/athens/home.html"

# Set to true to force an SSL redirect
# Env override: PROXY_FORCE_SSL
ForceSSL = false
Expand Down
101 changes: 101 additions & 0 deletions docs/content/configuration/home-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: Home template configuration
description: How to customize the home template
weight: 8
---

As of v0.14.0 Athens ships with a default, minimal HTML home page that advises users on how to connect to the proxy. It factors in whether `GoNoSumPatterns` is configured, and attempts
to build configuration for `GO_PROXY`. It relies on the users request Host header (on HTTP 1.1) or the Authority header (on HTTP 2) as well as whether the request was over TLS to advise
on configuring `GO_PROXY`. Lastly, the homepage provides a quick guide on how users can leverage the Athens API.

Of course, not all instructions will be this simple. Some installations may be reachable at different addresses in CI than for desktop users. In this case, and others where the default
home page does not make sense it is possible to override the template.

Do so by configuring `HomeTemplatePath` via the config or `ATHENS_HOME_TEMPLATE_PATH` to a location on disk with a Go HTML template or placing a template file at `/var/lib/athens/home.html`.

Athens automatically injects the following variables in templates:

| Setting | Source |
| :------ | :----- |
| `Host` | Built from the request Host (HTTP1) or Authority (HTTP2) header and presence of TLS. Includes ports. |
| `NoSumPatterns` | Comes directly from the configuration. |

Using these values is done by wrapping them in bracers with a dot prepended. Example: `{{ .Host }}`

For more advanced formatting read more about [Go HTML templates](https://pkg.go.dev/html/template).

```html
<!DOCTYPE html>
<html>
<head>
<title>Athens</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
pre {
background-color: #f4f4f4;
padding: 5px;
border-radius: 5px;
width: fit-content;
padding: 10px;
}
code {
background-color: #f4f4f4;
padding: 5px;
border-radius: 5px;
}
</style>
</head>
<body>

<h1>Welcome to Athens</h1>

<h2>Configuring your client</h2>
<pre>GOPROXY={{ .Host }},direct</pre>
{{ if .NoSumPatterns }}
<h3>Excluding checksum database</h3>
<p>Use the following GONOSUM environment variable to exclude checksum database:</p>
<pre>GONOSUM={{ .NoSumPatterns }}</pre>
{{ end }}

<h2>How to use the Athens API</h2>
<p>Use the <a href="/catalog">catalog</a> endpoint to get a list of all modules in the proxy</p>

<h3>List of versions</h3>
<p>This endpoint returns a list of versions that Athens knows about for <code>acidburn/htp</code>:</p>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@v/list</pre>

<h3>Version info</h3>
<p>This endpoint returns information about a specific version of a module:</p>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@v/v1.0.0.info</pre>
<p>This returns JSON with information about v1.0.0. It looks like this:
<pre>{
"Name": "v1.0.0",
"Short": "v1.0.0",
"Version": "v1.0.0",
"Time": "1972-07-18T12:34:56Z"
}</pre>

<h3>go.mod file</h3>
<p>This endpoint returns the go.mod file for a specific version of a module:</p>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@v/v1.0.0.mod</pre>
<p>This returns the go.mod file for version v1.0.0. If {{ .Host }}/github.com/acidburn/htp version v1.0.0 has no dependencies, the response body would look like this:</p>
<pre>module github.com/acidburn/htp</pre>

<h3>Module sources</h3>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@v/v1.0.0.zip</pre>
<p>This is what it sounds like — it sends back a zip file with the source code for the module in version v1.0.0.</p>

<h3>Latest</h3>
<pre>GET {{ .Host }}/github.com/acidburn/htp/@latest</pre>
<p>This endpoint returns the latest version of the module. If the version does not exist it should retrieve the hash of latest commit.</p>

</body>
</html>
```
4 changes: 0 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,6 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
Expand Down Expand Up @@ -771,12 +770,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand All @@ -786,7 +783,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down
Loading

0 comments on commit 8e3f2f7

Please sign in to comment.