Skip to content

Commit

Permalink
httptransport: update discovery endpoint
Browse files Browse the repository at this point in the history
This change switches to using the internal `apiError` function and
uses the "embed" package instead of generated code.

Signed-off-by: Hank Donnay <hdonnay@redhat.com>
  • Loading branch information
hdonnay committed Jul 7, 2022
1 parent bf530b8 commit e2e8b52
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 93 deletions.
34 changes: 14 additions & 20 deletions httptransport/discoveryhandler.go
Original file line number Diff line number Diff line change
@@ -1,48 +1,42 @@
package httptransport

import (
"bytes"
_ "embed" // for json and etag
"errors"
"io"
"net/http"
"strings"

je "github.com/quay/claircore/pkg/jsonerr"
)

//go:generate go run openapigen.go

var (
//go:embed openapi.json
openapiJSON []byte
//go:embed openapi.etag
openapiJSONEtag string
)

// DiscoveryHandler serves the embedded OpenAPI spec.
func DiscoveryHandler() http.Handler {
allow := []string{`application/json`, `application/vnd.oai.openapi+json`}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
resp := &je.Response{
Code: "method-not-allowed",
Message: "endpoint only allows GET",
}
je.Error(w, resp, http.StatusMethodNotAllowed)
apiError(w, http.StatusMethodNotAllowed, "endpoint only allows GET")
return
}
switch err := pickContentType(w, r, allow); {
case errors.Is(err, nil):
case errors.Is(err, ErrMediaType):
resp := &je.Response{
Code: "unknown accept type",
Message: "endpoint only allows " + strings.Join(allow, " or "),
}
je.Error(w, resp, http.StatusUnsupportedMediaType)
apiError(w, http.StatusUnsupportedMediaType, "unable to negotiate common media type for %v", allow)
return
default:
resp := &je.Response{
Code: "unknown other error",
Message: err.Error(),
}
je.Error(w, resp, http.StatusBadRequest)
apiError(w, http.StatusInternalServerError, "unexpected error: %v", err)
return
}
w.Header().Set("etag", _openapiJSONEtag)
w.Header().Set("etag", openapiJSONEtag)
var err error
defer writerError(w, &err)()
_, err = io.WriteString(w, _openapiJSON)
_, err = io.Copy(w, bytes.NewReader(openapiJSON))
})
}
8 changes: 0 additions & 8 deletions httptransport/discoveryhandler_gen.go

This file was deleted.

44 changes: 25 additions & 19 deletions httptransport/discoveryhandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package httptransport

import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

Expand Down Expand Up @@ -64,26 +64,32 @@ func TestDiscoveryFailure(t *testing.T) {
}

func TestEmbedding(t *testing.T) {
ctx, done := context.WithCancel(context.Background())
defer done()

var gend, written bytes.Buffer
cmd := exec.CommandContext(ctx, "go", "run", "openapigen.go", "-out", "/dev/stdout")
cmd.Stdout = &gend
d := t.TempDir()
var buf bytes.Buffer
cmd := exec.Command("go", "run", "openapigen.go", "-in", "../openapi.yaml", "-out", d)
cmd.Stdout = &buf
cmd.Stderr = &buf
t.Log(cmd.Args)
if err := cmd.Run(); err != nil {
t.Fatal(err)
}
f, err := os.Open("discoveryhandler_gen.go")
if err != nil {
t.Fatal(err)
}
defer f.Close()
if _, err := written.ReadFrom(f); err != nil {
t.Fatal(err)
t.Error(err)
t.Error(buf.String())
}

if got, want := gend.String(), written.String(); !cmp.Equal(got, want) {
t.Error(cmp.Diff(got, want, cmpopts.AcyclicTransformer("normalizeWhitespace", func(s string) []string { return strings.Split(s, "\n") })))
t.Log("\n\tYou probably edited the openapi.yaml and forgot to run `go generate` here.")
for _, n := range []string{
"openapi.json", "openapi.etag"} {
new, err := os.ReadFile(filepath.Join(d, n))
if err != nil {
t.Error(err)
continue
}
old, err := os.ReadFile(n)
if err != nil {
t.Error(err)
continue
}
if got, want := string(new), string(old); !cmp.Equal(got, want) {
t.Error(cmp.Diff(got, want, cmpopts.AcyclicTransformer("normalizeWhitespace", func(s string) []string { return strings.Split(s, "\n") })))
t.Log("\n\tYou probably edited the openapi.yaml and forgot to run `go generate` here.")
}
}
}
1 change: 1 addition & 0 deletions httptransport/openapi.etag
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"eee376f6eb502c8b9e413b131b81d700d185927f607474fdf80a237a1cc5294d"
1 change: 1 addition & 0 deletions httptransport/openapi.json

Large diffs are not rendered by default.

58 changes: 12 additions & 46 deletions httptransport/openapigen.go
Original file line number Diff line number Diff line change
@@ -1,41 +1,27 @@
//go:build tools
// +build tools

// Openapigen is a script to take the OpenAPI YAML file, turn it into a JSON
// document, and embed it into a source file for easy deployment.
// document, and write out files for use with the "embed" package.
package main

import (
"bytes"
"crypto/sha256"
"encoding/json"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io"
"log"
"os"
"path/filepath"
"strings"

"gopkg.in/yaml.v3"
)

const template = `
// Code generated by openapigen.go DO NOT EDIT.
package main
const (
_openapiJSON = ""
_openapiJSONEtag = ""
)
`

func main() {
inFile := flag.String("in", "../openapi.yaml", "input YAML file")
outFile := flag.String("out", "discoveryhandler_gen.go", "output go file")
pkgName := flag.String("name", "httptransport", "package name for generated file")
outDir := flag.String("out", ".", "output directory")
flag.Parse()

inF, err := os.Open(*inFile)
Expand All @@ -56,40 +42,20 @@ func main() {
}
ck := sha256.Sum256(embed)

fs := token.NewFileSet()
file, err := parser.ParseFile(fs, filepath.Base(*outFile), strings.NewReader(template), parser.ParseComments)
outF, err := os.OpenFile(filepath.Join(*outDir, `openapi.json`), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
ast.Inspect(file, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.File:
x.Name = ast.NewIdent(*pkgName)
case *ast.ValueSpec:
id := x.Names[0]
var v string
switch id.Name {
case "_openapiJSON":
v = fmt.Sprintf("%#q", string(embed))
case "_openapiJSONEtag":
v = fmt.Sprintf("`\"%x\"`", ck)
default:
return true
}
x.Values = []ast.Expr{&ast.BasicLit{
Kind: token.STRING,
Value: v,
}}
}
return true
})

outF, err := os.OpenFile(*outFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
defer outF.Close()
if _, err := io.Copy(outF, bytes.NewReader(embed)); err != nil {
log.Fatal(err)
}
outF, err = os.OpenFile(filepath.Join(*outDir, `openapi.etag`), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer outF.Close()
if err := format.Node(outF, fs, file); err != nil {
if _, err := fmt.Fprintf(outF, `"%x"`, ck); err != nil {
log.Fatal(err)
}
}
Expand Down

0 comments on commit e2e8b52

Please sign in to comment.