generated from templates/go-generic
feat: project setup
- Set the project's placeholder name to indieauth-server. - Update the magefiles and CI workflow. - Add the version command to print the app's build information. - Add the serve command to run the HTTP server.
This commit is contained in:
parent
7751cf2fe8
commit
4fdad2d805
16 changed files with 312 additions and 39 deletions
|
@ -1,26 +1,48 @@
|
||||||
---
|
---
|
||||||
name: Workflow
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
types:
|
types:
|
||||||
- opened
|
- opened
|
||||||
- synchronize
|
- synchronize
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
tests:
|
||||||
|
name: Tests
|
||||||
if: ${{ ! github.event.pull_request.draft }}
|
if: ${{ ! github.event.pull_request.draft }}
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
env:
|
|
||||||
GO_TEST_VERBOSE: "1"
|
|
||||||
GO_TEST_COVER: "1"
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: https://code.forgejo.org/actions/checkout@v4
|
uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
- name: Test
|
- name: Run tests
|
||||||
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
|
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
|
||||||
with:
|
with:
|
||||||
target: test
|
target: test
|
||||||
env:
|
env:
|
||||||
PROJECT_TEST_VERBOSE: "1"
|
PROJECT_TEST_VERBOSE: "1"
|
||||||
PROJECT_TEST_COVER: "1"
|
PROJECT_TEST_COVER: "1"
|
||||||
|
- name: Run gosec
|
||||||
|
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
|
||||||
|
with:
|
||||||
|
target: gosec
|
||||||
|
|
||||||
|
style:
|
||||||
|
name: Style
|
||||||
|
if: ${{ ! github.event.pull_request.draft }}
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: https://code.forgejo.org/actions/checkout@v4
|
||||||
|
- name: Check formatting
|
||||||
|
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
|
||||||
|
with:
|
||||||
|
target: gofmt
|
||||||
|
env:
|
||||||
|
PROJECT_FAIL_ON_FORMATTING: 1
|
||||||
|
- name: Run staticcheck
|
||||||
|
uses: https://codeflow.dananglin.me.uk/actions/mage-ci@main
|
||||||
|
with:
|
||||||
|
target: staticcheck
|
||||||
|
|
|
@ -13,6 +13,14 @@ output:
|
||||||
sort-results: true
|
sort-results: true
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
depguard:
|
||||||
|
rules:
|
||||||
|
main:
|
||||||
|
files:
|
||||||
|
- $all
|
||||||
|
allow:
|
||||||
|
- $gostd
|
||||||
|
- codeflow.dananglin.me.uk/apollo/indieauth-server
|
||||||
lll:
|
lll:
|
||||||
line-length: 140
|
line-length: 140
|
||||||
|
|
||||||
|
|
23
cmd/indieauth-server/main.go
Normal file
23
cmd/indieauth-server/main.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/executors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Set up logging
|
||||||
|
loggingLevel := new(slog.LevelVar)
|
||||||
|
|
||||||
|
slogOpts := slog.HandlerOptions{Level: loggingLevel}
|
||||||
|
|
||||||
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slogOpts))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
loggingLevel.Set(slog.LevelInfo)
|
||||||
|
|
||||||
|
if err := executors.Execute(os.Args[1:]); err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module codeflow.dananglin.me.uk/apollo/indieauth-server
|
||||||
|
|
||||||
|
go 1.23.2
|
24
internal/executors/command.go
Normal file
24
internal/executors/command.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package executors
|
||||||
|
|
||||||
|
type command struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommand(args []string) command {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return command{
|
||||||
|
name: "help",
|
||||||
|
args: make([]string, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 1 {
|
||||||
|
return command{
|
||||||
|
name: args[0],
|
||||||
|
args: make([]string, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return command{name: args[0], args: args[1:]}
|
||||||
|
}
|
9
internal/executors/errors.go
Normal file
9
internal/executors/errors.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package executors
|
||||||
|
|
||||||
|
type UnrecognisedCommandError struct {
|
||||||
|
command string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e UnrecognisedCommandError) Error() string {
|
||||||
|
return "unrecognised command: " + e.command
|
||||||
|
}
|
17
internal/executors/executors.go
Normal file
17
internal/executors/executors.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package executors
|
||||||
|
|
||||||
|
func Execute(args []string) error {
|
||||||
|
command := newCommand(args)
|
||||||
|
|
||||||
|
executorFuncMap := map[string]func(args []string) error{
|
||||||
|
"serve": executeServeCommand,
|
||||||
|
"version": executeVersionCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
executeFunc, ok := executorFuncMap[command.name]
|
||||||
|
if !ok {
|
||||||
|
return UnrecognisedCommandError{command.name}
|
||||||
|
}
|
||||||
|
|
||||||
|
return executeFunc(command.args)
|
||||||
|
}
|
1
internal/executors/help.go
Normal file
1
internal/executors/help.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package executors
|
55
internal/executors/serve.go
Normal file
55
internal/executors/serve.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package executors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/info"
|
||||||
|
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/router"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serveExecutor struct {
|
||||||
|
*flag.FlagSet
|
||||||
|
|
||||||
|
address string
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeServeCommand(args []string) error {
|
||||||
|
executorName := "serve"
|
||||||
|
|
||||||
|
executor := serveExecutor{
|
||||||
|
FlagSet: flag.NewFlagSet(executorName, flag.ExitOnError),
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.StringVar(&executor.address, "address", "0.0.0.0:8080", "The address that the server will listen on")
|
||||||
|
|
||||||
|
if err := executor.Parse(args); err != nil {
|
||||||
|
return fmt.Errorf("(%s) flag parsing error: %w", executorName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := executor.execute(); err != nil {
|
||||||
|
return fmt.Errorf("(%s) execution error: %w", executorName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *serveExecutor) execute() error {
|
||||||
|
server := http.Server{
|
||||||
|
Addr: e.address,
|
||||||
|
Handler: router.NewServeMux(),
|
||||||
|
ReadHeaderTimeout: 1 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info(info.ApplicationName+" is listening for web requests", "address", e.address)
|
||||||
|
|
||||||
|
err := server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error running the server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
58
internal/executors/version.go
Normal file
58
internal/executors/version.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package executors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"codeflow.dananglin.me.uk/apollo/indieauth-server/internal/info"
|
||||||
|
)
|
||||||
|
|
||||||
|
type versionExecutor struct {
|
||||||
|
*flag.FlagSet
|
||||||
|
|
||||||
|
showFullVersion bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeVersionCommand(args []string) error {
|
||||||
|
executorName := "version"
|
||||||
|
|
||||||
|
executor := versionExecutor{
|
||||||
|
FlagSet: flag.NewFlagSet(executorName, flag.ExitOnError),
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.BoolVar(&executor.showFullVersion, "full", false, "Print the applications full build information")
|
||||||
|
|
||||||
|
if err := executor.Parse(args); err != nil {
|
||||||
|
return fmt.Errorf("(%s) flag parsing error: %w", executorName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.printVersion()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *versionExecutor) printVersion() {
|
||||||
|
if !e.showFullVersion {
|
||||||
|
fmt.Fprintf(os.Stdout, "%s %s\n", info.ApplicationName, info.BinaryVersion)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
builder.WriteString(info.ApplicationName + "\n\n")
|
||||||
|
|
||||||
|
tableWriter := tabwriter.NewWriter(&builder, 0, 4, 1, ' ', 0)
|
||||||
|
|
||||||
|
_, _ = tableWriter.Write([]byte("Version:" + "\t" + info.BinaryVersion + "\n"))
|
||||||
|
_, _ = tableWriter.Write([]byte("Git commit:" + "\t" + info.GitCommit + "\n"))
|
||||||
|
_, _ = tableWriter.Write([]byte("Go version:" + "\t" + info.GoVersion + "\n"))
|
||||||
|
_, _ = tableWriter.Write([]byte("Build date:" + "\t" + info.BuildTime + "\n"))
|
||||||
|
|
||||||
|
_ = tableWriter.Flush()
|
||||||
|
|
||||||
|
_, _ = os.Stdout.WriteString(builder.String())
|
||||||
|
}
|
12
internal/info/info.go
Normal file
12
internal/info/info.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package info
|
||||||
|
|
||||||
|
const (
|
||||||
|
ApplicationName string = "indieauth-server"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
BinaryVersion string //nolint:gochecknoglobals
|
||||||
|
BuildTime string //nolint:gochecknoglobals
|
||||||
|
GoVersion string //nolint:gochecknoglobals
|
||||||
|
GitCommit string //nolint:gochecknoglobals
|
||||||
|
)
|
9
internal/router/router.go
Normal file
9
internal/router/router.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func NewServeMux() *http.ServeMux {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
return mux
|
||||||
|
}
|
5
magefiles/go.mod
Normal file
5
magefiles/go.mod
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module codeflow.dananglin.me.uk/apollo/indieauth-server/magefiles
|
||||||
|
|
||||||
|
go 1.23.2
|
||||||
|
|
||||||
|
require github.com/magefile/mage v1.15.0
|
2
magefiles/go.sum
Normal file
2
magefiles/go.sum
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||||
|
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/magefile/mage/mg"
|
"github.com/magefile/mage/mg"
|
||||||
|
@ -14,13 +15,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
app = "binary"
|
app = "indieauth-server"
|
||||||
defaultInstallPrefix = "/usr/local"
|
defaultInstallPrefix = "/usr/local"
|
||||||
|
|
||||||
envInstallPrefix = "PROJECT_INSTALL_PREFIX"
|
envInstallPrefix = "PROJECT_INSTALL_PREFIX"
|
||||||
envTestVerbose = "PROJECT_TEST_VERBOSE"
|
envTestVerbose = "PROJECT_TEST_VERBOSE"
|
||||||
envTestCover = "PROJECT_TEST_COVER"
|
envTestCover = "PROJECT_TEST_COVER"
|
||||||
envBuildRebuildAll = "PROJECT_BUILD_REBUILD_ALL"
|
envBuildRebuildAll = "PROJECT_BUILD_REBUILD_ALL"
|
||||||
envBuildVerbose = "PROJECT_BUILD_VERBOSE"
|
envBuildVerbose = "PROJECT_BUILD_VERBOSE"
|
||||||
|
envFailOnFormatting = "PROJECT_FAIL_ON_FORMATTING"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -52,11 +55,46 @@ func Lint() error {
|
||||||
return sh.RunV("golangci-lint", "run", "--color", "always")
|
return sh.RunV("golangci-lint", "run", "--color", "always")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Gosec() error {
|
||||||
|
return sh.RunV("gosec", "./...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Staticcheck() error {
|
||||||
|
return sh.RunV("staticcheck", "./...")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gofmt checks the code for formatting.
|
||||||
|
// To fail on formatting set PROJECT_FAIL_ON_FORMATTING=1
|
||||||
|
func Gofmt() error {
|
||||||
|
output, err := sh.Output("go", "fmt", "./...")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedFiles := ""
|
||||||
|
|
||||||
|
for _, file := range strings.Split(output, "\n") {
|
||||||
|
formattedFiles += "\n- " + file
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv(envFailOnFormatting) != "1" {
|
||||||
|
fmt.Println(formattedFiles)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(output) != 0 {
|
||||||
|
return fmt.Errorf("The following files needed to be formatted: %s", formattedFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Build build the executable.
|
// Build build the executable.
|
||||||
// To rebuild packages that are already up-to-date set PROJECT_BUILD_REBUILD_ALL=1
|
// To rebuild packages that are already up-to-date set PROJECT_BUILD_REBUILD_ALL=1
|
||||||
// To enable verbose mode set PROJECT_BUILD_VERBOSE=1
|
// To enable verbose mode set PROJECT_BUILD_VERBOSE=1
|
||||||
func Build() error {
|
func Build() error {
|
||||||
main := "main.go"
|
main := "./cmd/" + app
|
||||||
flags := ldflags()
|
flags := ldflags()
|
||||||
build := sh.RunCmd("go", "build")
|
build := sh.RunCmd("go", "build")
|
||||||
args := []string{"-ldflags=" + flags, "-o", binary}
|
args := []string{"-ldflags=" + flags, "-o", binary}
|
||||||
|
@ -110,10 +148,21 @@ func Clean() error {
|
||||||
|
|
||||||
// ldflags returns the build flags.
|
// ldflags returns the build flags.
|
||||||
func ldflags() string {
|
func ldflags() string {
|
||||||
ldflagsfmt := "-s -w -X main.binaryVersion=%s -X main.gitCommit=%s -X main.goVersion=%s -X main.buildTime=%s"
|
versionPackage := "codeflow.dananglin.me.uk/apollo/indieauth-server/internal/info"
|
||||||
|
binaryVersionVar := versionPackage + "." + "BinaryVersion"
|
||||||
|
gitCommitVar := versionPackage + "." + "GitCommit"
|
||||||
|
goVersionVar := versionPackage + "." + "GoVersion"
|
||||||
|
buildTimeVar := versionPackage + "." + "BuildTime"
|
||||||
|
ldflagsfmt := "-s -w -X %s=%s -X %s=%s -X %s=%s -X %s=%s"
|
||||||
buildTime := time.Now().UTC().Format(time.RFC3339)
|
buildTime := time.Now().UTC().Format(time.RFC3339)
|
||||||
|
|
||||||
return fmt.Sprintf(ldflagsfmt, version(), gitCommit(), runtime.Version(), buildTime)
|
return fmt.Sprintf(
|
||||||
|
ldflagsfmt,
|
||||||
|
binaryVersionVar, version(),
|
||||||
|
gitCommitVar, gitCommit(),
|
||||||
|
goVersionVar, runtime.Version(),
|
||||||
|
buildTimeVar, buildTime,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// version returns the latest git tag using git describe.
|
// version returns the latest git tag using git describe.
|
||||||
|
|
24
main.go
24
main.go
|
@ -1,24 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
binaryVersion string
|
|
||||||
buildTime string
|
|
||||||
goVersion string
|
|
||||||
gitCommit string
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := run(); err != nil {
|
|
||||||
fmt.Printf("ERROR: %v.\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in a new issue