Skip to content

Commit

Permalink
style: Only run spec generation in dev/test
Browse files Browse the repository at this point in the history
Previously it could have run if the binary was run from any directory
which contains a `docs` or `.git` directory.
  • Loading branch information
dnephin committed Mar 22, 2022
1 parent 46ae97a commit 3a0ba37
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 88 deletions.
2 changes: 0 additions & 2 deletions docs/api/openapi3.json
Original file line number Diff line number Diff line change
Expand Up @@ -3321,11 +3321,9 @@
"operationId": "ListUsers",
"parameters": [
{
"example": "email@example.com",
"in": "query",
"name": "email",
"schema": {
"example": "email@example.com",
"type": "string"
}
},
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ func canonicalPath(in string) (string, error) {
return abs, nil
}

// TODO: remove
func newOpenAPICmd() *cobra.Command {
cmd := &cobra.Command{
Use: "openapi",
Expand Down
100 changes: 31 additions & 69 deletions internal/server/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,16 @@ func getFuncName(i interface{}) string {
nameParts := strings.Split(name, ".")
name = nameParts[len(nameParts)-1]
name = strings.TrimSuffix(name, "-fm")

return name
}

func orderedTagNames() []string {
tagNames := []string{}
tagNames := make([]string, 0, len(funcPartialNameToTagNames))
for k := range funcPartialNameToTagNames {
tagNames = append(tagNames, k)
}

sort.Strings(tagNames)

return tagNames
}

Expand Down Expand Up @@ -218,98 +216,62 @@ func buildProperty(f reflect.StructField, t, parent reflect.Type, parentSchema *
}
}

func isDev() bool {
dirs := []string{".git", "docs"}
for _, dir := range dirs {
info, err := os.Stat(dir)
if err != nil {
return false
}

if !info.IsDir() {
return false
}
}

return true
}

func generateOpenAPI() {
if !isDev() {
return
}

func writeOpenAPISpec(version string, out io.Writer) error {
openAPISchema.OpenAPI = "3.0.0"
openAPISchema.Info = &openapi3.Info{
Title: "Infra API",
Version: time.Now().Format("2006-01-02"),
Version: version,
Description: "Infra API",
License: &openapi3.License{Name: "Apache 2.0", URL: "https://www.apache.org/licenses/LICENSE-2.0.html"},
}
openAPISchema.Servers = append(openAPISchema.Servers, &openapi3.Server{
URL: "https://api.infrahq.com",
})
encoder := json.NewEncoder(out)
encoder.SetIndent("", " ")

if changedSinceLastRun() {
f2, err := os.Create("docs/api/openapi3.json")
if err != nil {
panic(err)
}
defer f2.Close()
enc2 := json.NewEncoder(f2)
enc2.SetIndent("", " ")

if err := enc2.Encode(openAPISchema); err != nil {
panic(err)
}
if err := encoder.Encode(openAPISchema); err != nil {
return fmt.Errorf("failed to write schema: %w", err)
}
return nil
}

func changedSinceLastRun() bool {
origVersion := openAPISchema.Info.Version
defer func() {
openAPISchema.Info.Version = origVersion
}()

// read last file
f, err := os.Open("docs/api/openapi3.json")
func writeOpenAPISpecToFile(filename string) error {
old, err := readOpenAPISpec(filename)
if err != nil {
panic("expected docs/api/openapi3.json to exist but didn't find it: " + err.Error())
return err
}
defer f.Close()

oldOpenAPISchema := openapi3.T{}
version := time.Now().Format("2006-01-02")
old.Info.Version = version

err = json.NewDecoder(f).Decode(&oldOpenAPISchema)
if err != nil {
panic("couldn't parse last openapi schema: " + err.Error())
var buf bytes.Buffer
if err := writeOpenAPISpec(version, &buf); err != nil {
return err
}

oldDate := oldOpenAPISchema.Info.Version

openAPISchema.Info.Version = oldDate

_, err = f.Seek(0, 0)
if err != nil {
panic(err)
if reflect.DeepEqual(openAPISchema, old) {
// no changes to the schema
return nil
}

bufLast := &bytes.Buffer{}
// nolint: gosec // 0644 is the right mode
return os.WriteFile(filename, buf.Bytes(), 0o644)
}

func readOpenAPISpec(filename string) (openapi3.T, error) {
spec := openapi3.T{}

_, err = io.Copy(bufLast, f)
fh, err := os.Open(filename)
if err != nil {
panic("couldn't read file contents: " + err.Error())
return spec, fmt.Errorf("failed to create file: %w", err)
}
defer fh.Close()

bufTmp := &bytes.Buffer{}
enc2 := json.NewEncoder(bufTmp)
enc2.SetIndent("", " ")

if err := enc2.Encode(openAPISchema); err != nil {
panic(err)
if err := json.NewDecoder(fh).Decode(&spec); err != nil {
return spec, fmt.Errorf("failed to parse last openapi schema from %s: %w", filename, err)
}

return !bytes.Equal(bufLast.Bytes(), bufTmp.Bytes())
return spec, nil
}

func setTagInfo(f reflect.StructField, t, parent reflect.Type, schema, parentSchema *openapi3.Schema) {
Expand Down
25 changes: 10 additions & 15 deletions internal/server/openapi_test.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
package server

import (
"os"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestOpenAPIGen(t *testing.T) {
// must run from infra root dir
wd, err := os.Getwd()
require.NoError(t, err)

parts := strings.Split(wd, string(os.PathSeparator))

if parts[len(parts)-1] != "infra" {
err := os.Chdir("../..")
require.NoError(t, err)
}

s := &Server{}
// TestWriteOpenAPISpec is not really a test. It's a way of ensuring the openapi
// spec is updated.
// TODO: replace this with a test that uses golden, and a CI check to make sure the
// file in git matches the source code.
func TestWriteOpenAPISpec(t *testing.T) {
s := Server{}
s.GenerateRoutes()

filename := "../../docs/api/openapi3.json"
err := writeOpenAPISpecToFile(filename)
require.NoError(t, err)
}
2 changes: 0 additions & 2 deletions internal/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ func (a *API) registerRoutes(router *gin.RouterGroup) {

get(unauthorized, "/version", a.Version)
}

generateOpenAPI()
}

func get[Req, Res any](r *gin.RouterGroup, path string, handler ReqResHandlerFunc[Req, Res]) {
Expand Down

0 comments on commit 3a0ba37

Please sign in to comment.