feat(go-swagger): Refactoring to use go-swagger templating system for most of the work
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Matthieu 'JP' DERASSE 2023-01-01 16:59:41 +00:00
parent d997c0035d
commit 65add2a61d
Signed by: mderasse
GPG Key ID: 55141C777B16A705
29 changed files with 538 additions and 21 deletions

View File

@ -155,7 +155,7 @@ linters-settings:
# Select the Go version to target. The default is '1.13'.
go: "1.18.5"
misspell:
misspell: {}
# Correct spellings using locale preferences for US or UK.
# Default is to use a neutral variety of English.
# Setting locale to US will correct the British spelling of 'colour' to 'color'.

4
go.mod
View File

@ -4,6 +4,8 @@ go 1.19
require (
github.com/blang/semver v3.5.1+incompatible
github.com/go-openapi/analysis v0.21.4
github.com/go-openapi/loads v0.21.2
github.com/go-swagger/go-swagger v0.30.3
github.com/jessevdk/go-flags v1.5.0
github.com/juju/errors v1.0.0
@ -19,12 +21,10 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/runtime v0.24.2 // indirect
github.com/go-openapi/spec v0.20.7 // indirect
github.com/go-openapi/strfmt v0.21.3 // indirect

View File

@ -6,3 +6,10 @@ const templateDirectory = "templates/go-swagger"
// mergeYamlFileName is the name of the file that will contain the final yaml spec of the API.
// that file will always be deleted after generation.
const mergeYamlFileName = "api-temp.yaml"
// regeneratedFiles is a list of files / directory that are automatically regenerated by go-swagger.
// They will be automatically deleted by the generate api process.
var regeneratedFiles = []string{
"restapi",
"models",
}

View File

@ -1,6 +1,9 @@
package go_swagger
import (
"os"
"path/filepath"
"github.com/juju/errors"
"git.dev.m-and-m.ovh/mderasse/gouick/helpers"
@ -13,13 +16,34 @@ import (
func (a APIType) GenerateAPI(path string, config *models.Config) error {
log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
// delete file that need to be regenerated
err := a.deleteRegeneratedFiles(path)
if err != nil {
log.Error("Fail to delete Files that will be regenerated by Go Swagger")
return errors.Trace(err)
}
err = a.createDirectories(path)
if err != nil {
log.Error("Fail to create project directories")
return errors.Trace(err)
}
// Launch Go Swagger
err := a.executeGoSwagger(path)
err = a.executeGoSwagger(path)
if err != nil {
log.Error("Fail to Execute Go Swagger")
return errors.Trace(err)
}
// No use atm
// Generate files from spec
// err = a.generateFromSpec(path)
// if err != nil {
// log.Error("Fail to generate files from spec")
// return errors.Trace(err)
// }
// Delete api yaml temporary file
err = a.deleteTempApiYaml(path)
if err != nil {
@ -29,3 +53,44 @@ func (a APIType) GenerateAPI(path string, config *models.Config) error {
return nil
}
// deleteRegeneratedFiles will delete files that will be regenerated by go swagger.
func (a APIType) deleteRegeneratedFiles(path string) error {
log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
for _, file := range regeneratedFiles {
tempFilePath := filepath.Join(path, file)
exist, err := helpers.FileExists(tempFilePath, true)
if err != nil {
return errors.Trace(err)
}
if !exist {
log.Debug("File/Directory doesn't exist already")
return nil
}
err = os.RemoveAll(tempFilePath)
if err != nil {
log.Errorf("fail to delete file/dir in %s", tempFilePath)
return errors.Trace(err)
}
}
return nil
}
// // generateFromSpec will read the swagger spec and create cmd server, handlers, test ...
// func (a APIType) generateFromSpec(path string) error {
// log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
// tempFilePath := filepath.Join(path, mergeYamlFileName)
// specData, err := spec.NewSpec(tempFilePath)
// if err != nil {
// return errors.Trace(err)
// }
// fmt.Printf("%#v", specData.GetOperationGroups())
// return nil
// }

View File

@ -15,7 +15,7 @@ import (
func (a APIType) GenerateDockerfile(path string, config *models.Config) error {
log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
templatePath := filepath.Join(templateDirectory, "Dockerfile.tmpl")
templatePath := filepath.Join(templateDirectory, "custom/Dockerfile.tmpl")
savePath := filepath.Join(path, "Dockerfile")
data, err := a.getStdTemplate(path, config)

View File

@ -15,7 +15,7 @@ import (
func (a APIType) GenerateLauncher(path string, config *models.Config) error {
log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
templatePath := filepath.Join(templateDirectory, "launcher.sh.tmpl")
templatePath := filepath.Join(templateDirectory, "custom/launcher.sh.tmpl")
savePath := filepath.Join(path, "launcher.sh")
data, err := a.getStdTemplate(path, config)

View File

@ -16,7 +16,7 @@ import (
func (a APIType) GenerateMakefile(path string, config *models.Config) error {
log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
templatePath := filepath.Join(templateDirectory, "Makefile.tmpl")
templatePath := filepath.Join(templateDirectory, "custom/Makefile.tmpl")
savePath := filepath.Join(path, "Makefile")
data, err := a.getStdTemplate(path, config)

View File

@ -16,7 +16,7 @@ import (
func (a APIType) GenerateReadme(path string, config *models.Config) error {
log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
templatePath := filepath.Join(templateDirectory, "Readme.md.tmpl")
templatePath := filepath.Join(templateDirectory, "custom/Readme.md.tmpl")
savePath := filepath.Join(path, "Readme.md")
data, err := a.getStdTemplate(path, config)

View File

@ -72,6 +72,7 @@ func (a APIType) executeGoSwagger(path string) error {
// goSwaggerMixin will call go-swagger code to merge api yaml files.
func (a APIType) goSwaggerMixin(path string, primaryFile string, mixinFiles []string) ([]string, error) {
log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
mixinSpec := commands.MixinSpec{
Compact: false,
@ -86,16 +87,24 @@ func (a APIType) goSwaggerMixin(path string, primaryFile string, mixinFiles []st
// goSwaggerGenerate will generate all app files as go-swagger binary will.
func (a APIType) goSwaggerGenerate(path string) error {
log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
execDirectory, err := helpers.GetExecutableDirectory()
if err != nil {
log.Error("Fail to get Gouick directory")
return errors.Trace(err)
}
generateCmd := commands.Generate{
Server: &generate.Server{},
}
generateCmd.Server.Shared.Spec = flags.Filename(filepath.Join(path, mergeYamlFileName))
//generateCmd.Server.Shared.Target = flags.Filename(path)
generateCmd.Server.ServerPackage = "restapi"
generateCmd.Server.Models.ModelPackage = "models"
generateCmd.Server.Operations.APIPackage = "operations"
generateCmd.Server.Shared.StrictResponders = true
generateCmd.Server.Shared.Template = "stratoscale"
generateCmd.Server.Shared.StrictResponders = false
generateCmd.Server.Shared.TemplateDir = flags.Filename(filepath.Join(execDirectory, "templates/go-swagger/app/templates/"))
generateCmd.Server.Shared.ConfigFile = flags.Filename(filepath.Join(execDirectory, "templates/go-swagger/app/config.yaml"))
return generateCmd.Server.Execute(nil)
}

View File

@ -7,6 +7,7 @@ import (
"github.com/juju/errors"
"git.dev.m-and-m.ovh/mderasse/gouick/helpers"
"git.dev.m-and-m.ovh/mderasse/gouick/models"
log "github.com/sirupsen/logrus"
@ -14,6 +15,7 @@ import (
// getProcesses will read cmd directory and guess the app processes.
func (a APIType) getProcesses(path string) ([]*models.Process, error) {
log.Debugf("Starting %s - %s", a.GetName(), helpers.GetCurrentFuncName())
cmdPath := filepath.Join(path, "cmd")
cmdDirectories, err := filepath.Glob(filepath.Join(cmdPath, "*"))

View File

@ -25,15 +25,15 @@ func (a APIType) createDefaultAPIYamls(path string, config *models.Config) error
templateFileList := []templateFileStruct{
{
savePath: filepath.Join(path, "api.yaml"),
templatePath: filepath.Join(templateDirectory, "api.yaml.tmpl"),
templatePath: filepath.Join(templateDirectory, "custom/api.yaml.tmpl"),
},
{
savePath: filepath.Join(path, "api/001-general.yaml"),
templatePath: filepath.Join(templateDirectory, "api/001-general.yaml.tmpl"),
templatePath: filepath.Join(templateDirectory, "custom/api/001-general.yaml.tmpl"),
},
{
savePath: filepath.Join(path, "api/001-monitoring.yaml"),
templatePath: filepath.Join(templateDirectory, "api/002-monitoring.yaml.tmpl"),
templatePath: filepath.Join(templateDirectory, "custom/api/002-monitoring.yaml.tmpl"),
},
}
@ -60,8 +60,7 @@ func (a APIType) deleteTempApiYaml(path string) error {
tempFilePath := filepath.Join(path, mergeYamlFileName)
// delete
exist, err := helpers.FileExists(tempFilePath)
exist, err := helpers.FileExists(tempFilePath, false)
if err != nil {
return errors.Trace(err)
}

View File

@ -29,7 +29,7 @@ func IsGouickDirectory(path string) (bool, error) {
// IsGouickProject will check if a gouick config file exist and readeable.
func IsGouickProject(path string) (bool, error) {
exist, err := FileExists(filepath.Join(path, configFile))
exist, err := FileExists(filepath.Join(path, configFile), false)
if err != nil {
return false, errors.Trace(err)
}
@ -42,7 +42,7 @@ func IsGouickProject(path string) (bool, error) {
}
// FileExists will check if a file exist and is readeable.
func FileExists(path string) (bool, error) {
func FileExists(path string, allowDirectory bool) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
if errors.Is(err, fs.ErrPermission) {
@ -53,7 +53,7 @@ func FileExists(path string) (bool, error) {
return false, errors.Trace(err)
}
}
if fileInfo.IsDir() {
if fileInfo.IsDir() && !allowDirectory {
return false, errors.NotValidf("%s is actually a directory", path)
}

View File

@ -10,7 +10,7 @@ import (
// IsGoProject check if we are in a golang project (have go.mod).
func IsGoProject(path string) (bool, error) {
exist, err := FileExists(filepath.Join(path, "go.mod"))
exist, err := FileExists(filepath.Join(path, "go.mod"), false)
if err != nil {
return false, errors.Trace(err)
}
@ -19,7 +19,7 @@ func IsGoProject(path string) (bool, error) {
return true, nil
}
exist, err = FileExists(filepath.Join(path, "go.sum"))
exist, err = FileExists(filepath.Join(path, "go.sum"), false)
if err != nil {
return false, errors.Trace(err)
}

View File

@ -0,0 +1,60 @@
layout:
application:
- name: configure
source: asset:serverConfigureapi
target: "{{ joinFilePath .Target .ServerPackage }}"
file_name: "configure_{{ .Name }}.go"
- name: main
source: asset:serverMain
target: "{{ joinFilePath .Target \"cmd\" (dasherize (pascalize .Name)) }}-server"
file_name: "main.go"
skip_exists: true
- name: embedded_spec
source: asset:swaggerJsonEmbed
target: "{{ joinFilePath .Target .ServerPackage }}"
file_name: "embedded_spec.go"
- name: server
source: asset:serverServer
target: "{{ joinFilePath .Target .ServerPackage }}"
file_name: "server.go"
- name: builder
source: asset:serverBuilder
target: "{{ joinFilePath .Target .ServerPackage .Package }}"
file_name: "{{ snakize (pascalize .Name) }}_api.go"
- name: doc
source: asset:serverDoc
target: "{{ joinFilePath .Target .ServerPackage }}"
file_name: "doc.go"
models:
- name: definition
source: asset:model
target: "{{ joinFilePath .Target .ModelPackage }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"
operations:
- name: parameters
source: asset:serverParameter
target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target .ServerPackage .APIPackage .Package }}{{ else }}{{ joinFilePath .Target .ServerPackage .Package }}{{ end }}"
file_name: "{{ (snakize (pascalize .Name)) }}_parameters.go"
- name: responses
source: asset:serverResponses
target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target .ServerPackage .APIPackage .Package }}{{ else }}{{ joinFilePath .Target .ServerPackage .Package }}{{ end }}"
file_name: "{{ (snakize (pascalize .Name)) }}_responses.go"
- name: handler
source: asset:serverOperation
target: "{{ if gt (len .Tags) 0 }}{{ joinFilePath .Target .ServerPackage .APIPackage .Package }}{{ else }}{{ joinFilePath .Target .ServerPackage .Package }}{{ end }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"
- name: apihandler
source: asset:apiHandler
target: "{{ joinFilePath .Target \"internal\" \"api\" .Package }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"
skip_exists: true
operation_groups:
- name: cmdtag
source: asset:serverTag
target: "{{ joinFilePath .Target \"cmd\" .MainPackage }}"
file_name: "tag_{{ (snakize (pascalize .Name)) }}.go"
skip_exists: true
- name: apitag
source: asset:apiTag
target: "{{ joinFilePath .Target \"internal\" \"api\" (snakize (pascalize .Name)) }}"
file_name: "{{ (snakize (pascalize .Name)) }}.go"

View File

@ -0,0 +1,33 @@
package {{ .Package }}
import (
"context"
"github.com/go-openapi/runtime/middleware"
"{{ index .DefaultImports "models"}}"
api "{{ index .DefaultImports .Package }}"
)
// {{ pascalize .Name }} will {{ .Summary }}.
//
// {{ .Method }} {{ .Path }}
func (m {{ pascalize .Package }}) {{ pascalize .Name }}(ctx context.Context, params api.{{ pascalize .Name }}Params) middleware.Responder {
{{ if eq .Name "getMonPing" }}
return api.NewGetMonPingOK().WithPayload(
&models.MonPing{
Details: &models.MonPingDetails{
AppVersion: "",
GitHash: "",
},
Status: &models.MonPingStatus{
Application: true,
Database: true,
},
},
)
{{ else }}
return middleware.NotImplemented("Not Implemented")
{{ end }}
}

View File

@ -0,0 +1,6 @@
// Code generated by go-swagger; DO NOT EDIT.
package {{ (snakize (pascalize .Name)) }}
// {{ pascalize .Name }} implement the {{ pascalize .Name }}API interface from restapi.
type {{ pascalize .Name }} struct{}

View File

@ -0,0 +1,222 @@
// Code generated by go-swagger; DO NOT EDIT.
{{ if .Copyright -}}// {{ comment .Copyright -}}{{ end }}
package {{ .APIPackage }}
import (
"context"
"crypto/tls"
"net/http"
"log"
"fmt"
"github.com/go-openapi/errors"
"github.com/go-openapi/loads"
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/runtime/security"
{{ imports .DefaultImports }}
{{ imports .Imports }}
)
{{ $package := .Package }}
type contextKey string
const AuthKey contextKey = "Auth"
{{ range .OperationGroups -}}
//go:generate mockery -name {{ pascalize .Name}}API -inpkg
/* {{ pascalize .Name }}API {{ .Description }} */
type {{ pascalize .Name }}API interface {
{{ range .Operations -}}
{{ if .Summary -}}
/* {{ pascalize .Name }} {{ .Summary }} */
{{ else if .Description -}}
/* {{ pascalize .Name }} {{ .Description }} */
{{ end -}}
{{ pascalize .Name }}(ctx context.Context, params {{.Package}}.{{ pascalize .Name }}Params) middleware.Responder
{{ end -}}
}
{{ end }}
// Config is configuration for Handler
type Config struct {
{{ range .OperationGroups -}}
{{ pascalize .Name }}API
{{ end -}}
Logger func(string, ...interface{})
// InnerMiddleware is for the handler executors. These do not apply to the swagger.json document.
// The middleware executes after routing but before authentication, binding and validation
InnerMiddleware func(http.Handler) http.Handler
// Authorizer is used to authorize a request after the Auth function was called using the "Auth*" functions
// and the principal was stored in the context in the "AuthKey" context value.
Authorizer func(*http.Request) error
{{ range .SecurityDefinitions -}}
{{ if .IsBasicAuth -}}
// Auth{{ pascalize .ID }} for basic authentication
Auth{{ pascalize .ID }} func(user string, pass string) ({{ if .PrincipalIsNullable }}*{{ end }}{{ .Principal }}, error)
{{ end -}}
{{ if .IsAPIKeyAuth -}}
// Auth{{ pascalize .ID }} Applies when the "{{ .Name }}" {{ .Source }} is set
Auth{{ pascalize .ID }} func(token string) ({{ if .PrincipalIsNullable }}*{{ end }}{{ .Principal }}, error)
{{ end }}
{{ if .IsOAuth2 -}}
// Auth{{ pascalize .ID }} For OAuth2 authentication
Auth{{ pascalize .ID }} func(token string, scopes []string) ({{ if .PrincipalIsNullable }}*{{ end }}{{ .Principal }}, error)
{{ end -}}
{{ end -}}
// Authenticator to use for all APIKey authentication
APIKeyAuthenticator func(string, string, security.TokenAuthentication) runtime.Authenticator
// Authenticator to use for all Bearer authentication
BasicAuthenticator func(security.UserPassAuthentication) runtime.Authenticator
// Authenticator to use for all Basic authentication
BearerAuthenticator func(string, security.ScopedTokenAuthentication) runtime.Authenticator
{{ range .Consumes -}}
{{ if .Implementation -}}
// {{ pascalize .Name }}Consumer is a {{ .Name }} consumer that will replace the default if not nil.
{{ pascalize .Name }}Consumer runtime.Consumer
{{ end -}}
{{ end -}}
}
// Handler returns an http.Handler given the handler configuration
// It mounts all the business logic implementers in the right routing.
func Handler(c Config) (http.Handler, error) {
h, _, err := HandlerAPI(c)
return h, err
}
// HandlerAPI returns an http.Handler given the handler configuration
// and the corresponding *{{ pascalize .Name }} instance.
// It mounts all the business logic implementers in the right routing.
func HandlerAPI(c Config) (http.Handler, *{{.Package}}.{{ pascalize .Name }}API, error) {
spec, err := loads.Analyzed(swaggerCopy(SwaggerJSON), "")
if err != nil {
return nil, nil, fmt.Errorf("analyze swagger: %v", err)
}
api := {{.Package}}.New{{ pascalize .Name }}API(spec)
api.ServeError = errors.ServeError
api.Logger = c.Logger
if c.APIKeyAuthenticator != nil {
api.APIKeyAuthenticator = c.APIKeyAuthenticator
}
if c.BasicAuthenticator != nil {
api.BasicAuthenticator = c.BasicAuthenticator
}
if c.BearerAuthenticator != nil {
api.BearerAuthenticator = c.BearerAuthenticator
}
{{ range .Consumes -}}
if c.{{ pascalize .Name }}Consumer != nil {
api.{{ pascalize .Name }}Consumer = c.{{ pascalize .Name }}Consumer
} else {
{{ if .Implementation -}}
api.{{ pascalize .Name }}Consumer = {{ .Implementation }}
{{ else }}
api.{{ pascalize .Name }}Consumer = runtime.ConsumerFunc(func(r io.Reader, target interface{}) error {
return errors.NotImplemented("{{.Name}} consumer has not yet been implemented")
})
{{ end -}}
}
{{ end -}}
{{ range .Produces -}}
{{ if .Implementation -}}
api.{{ pascalize .Name }}Producer = {{ .Implementation }}
{{ else -}}
api.{{ pascalize .Name }}Producer = runtime.ProducerFunc(func(w io.Writer, data interface{}) error {
return errors.NotImplemented("{{.Name}} producer has not yet been implemented")
})
{{ end -}}
{{ end -}}
{{ range .SecurityDefinitions -}}
{{ if .IsBasicAuth -}}
api.{{ pascalize .ID }}Auth = func(user string, pass string) ({{if .PrincipalIsNullable }}*{{ end }}{{.Principal}}, error) {
if c.Auth{{ pascalize .ID }} == nil {
{{- if eq .Principal "interface{}" }}
return "", nil
{{- else }}
panic("you specified a custom principal type, but did not provide the authenticator to provide this")
{{- end }}
}
return c.Auth{{ pascalize .ID }}(user, pass)
}
{{ end -}}
{{ if .IsAPIKeyAuth -}}
api.{{ pascalize .ID }}Auth = func(token string) ({{ if .PrincipalIsNullable }}*{{ end }}{{.Principal}}, error) {
if c.Auth{{ pascalize .ID }} == nil {
{{- if eq .Principal "interface{}" }}
return token, nil
{{- else }}
panic("you specified a custom principal type, but did not provide the authenticator to provide this")
{{- end }}
}
return c.Auth{{ pascalize .ID }}(token)
}
{{ end }}
{{ if .IsOAuth2 -}}
api.{{ pascalize .ID }}Auth = func(token string, scopes []string) ({{ if .PrincipalIsNullable }}*{{ end }}{{.Principal}}, error) {
if c.Auth{{ pascalize .ID }} == nil {
{{- if eq .Principal "interface{}" }}
return token, nil
{{- else }}
panic("you specified a custom principal type, but did not provide the authenticator to provide this")
{{- end }}
}
return c.Auth{{ pascalize .ID }}(token, scopes)
}
{{ end -}}
{{ end -}}
{{ if .SecurityDefinitions -}}
api.APIAuthorizer = authorizer(c.Authorizer)
{{ end -}}
{{ range .Operations -}}
api.{{if ne .Package $package}}{{pascalize .Package}}{{end}}{{ pascalize .Name }}Handler =
{{- .PackageAlias }}.{{ pascalize .Name }}HandlerFunc(func(params {{.PackageAlias}}.{{ pascalize .Name }}Params{{if .Authorized}}, principal {{ if .PrincipalIsNullable }}*{{ end }}{{ .Principal }}{{end}}) middleware.Responder {
ctx := params.HTTPRequest.Context()
{{ if .Authorized -}}
ctx = storeAuth(ctx, principal)
{{ end -}}
return c.{{pascalize .Package}}API.{{pascalize .Name}}(ctx, params)
})
{{ end -}}
api.ServerShutdown = func() { }
return api.Serve(c.InnerMiddleware), api, nil
}
// swaggerCopy copies the swagger json to prevent data races in runtime
func swaggerCopy(orig json.RawMessage) json.RawMessage {
c := make(json.RawMessage, len(orig))
copy(c, orig)
return c
}
// authorizer is a helper function to implement the runtime.Authorizer interface.
type authorizer func(*http.Request) error
func (a authorizer) Authorize(req *http.Request, principal interface{}) error {
if a == nil {
return nil
}
ctx := storeAuth(req.Context(), principal)
return a(req.WithContext(ctx))
}
func storeAuth(ctx context.Context, principal interface{}) context.Context {
return context.WithValue(ctx, AuthKey, principal)
}

View File

@ -0,0 +1,92 @@
package main
import (
"context"
"errors"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/sirupsen/logrus"
"git.dev.m-and-m.ovh/mderasse/gocommon/log"
"git.dev.m-and-m.ovh/mderasse/gocommon/tracing"
"{{ index .DefaultImports "restapi"}}"
)
// initTag is the signature of the function that tag initialization should respect.
type initTag func(*restapi.Config) error
// tagswill be fulfill by init function in tag_XXX.go.
var tags = map[string]initTag{}
func main() {
ctx := context.Background()
// Initialize logs
logger, err := log.Init()
if err != nil {
logrus.WithError(err).Fatal("fail to initialize gocommon.log")
}
// Initialize tracing
logger.Info("Initializing tracing")
tp, err := tracing.Init()
if err != nil {
logger.WithError(err).Fatal("fail to initialize gocommon.tracing")
}
// Configure go-swagger api
logger.Info("Configure go-swagger API")
conf := restapi.Config{
Logger: logger.Printf,
}
// Initialize Tags by calling initTag function that have been stored in tags variable (with init() in tag_XXX.go)
logger.Info("Initializing Tags")
for tag, initTagFunction := range tags {
if err := initTagFunction(&conf); err != nil {
logger.WithError(err).Fatalf("Failed to initialize tag %q", tag)
}
}
// Initialize API Handlers
logger.Info("Initializing API handlers")
h, _, err := restapi.HandlerAPI(conf)
if err != nil {
logger.WithError(err).Fatal("Failed to initialize API Handlers")
}
srv := http.Server{
Addr: ":8081",
Handler: h,
IdleTimeout: 30 * time.Second,
ReadHeaderTimeout: 2 * time.Second,
ReadTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
logger.WithError(err).Fatalf("HTTP server error: %v", err)
}
logger.Info("Stopped serving new connections.")
}()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
shutdownCtx, shutdownRelease := context.WithTimeout(ctx, 10*time.Second)
defer shutdownRelease()
if err := srv.Shutdown(shutdownCtx); err != nil {
logger.WithError(err).Fatalf("HTTP shutdown error: %v", err)
}
tp.Shutdown(ctx)
logger.Info("Graceful shutdown complete.")
}

View File

@ -0,0 +1,9 @@
// Code generated by go-swagger; DO NOT EDIT.
{{ if .Copyright -}}// {{ comment .Copyright -}}{{ end }}
package {{ .APIPackage }}
// this file is intentionally empty. Otherwise go-swagger will generate a server which we don't want

View File

@ -0,0 +1,13 @@
package main
import (
"{{ .GenCommon.TargetImportPath }}/internal/api/{{ (dasherize (pascalize .Name)) }}"
"{{ .GenCommon.TargetImportPath }}/restapi"
)
func init() {
tags["{{ pascalize .Name }}"] = func(c *restapi.Config) error {
c.{{ pascalize .Name }}API = {{ (snakize (pascalize .Name)) }}.{{ pascalize .Name }}{}
return nil
}
}

View File