Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions internal/text/text.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package text

import (
"fmt"
"reflect"
"strings"
)

func Marshal(v any) (string, error) {
return marshal(v, 0)
}

func marshal(v any, level int) (string, error) {
rv := reflect.ValueOf(v)
rt := reflect.TypeOf(v)
if rt.Kind() == reflect.Pointer {
rv = rv.Elem()
rt = rt.Elem()
}
if rt.Kind() != reflect.Struct {
return "", fmt.Errorf("structs are only supported")

Check failure on line 21 in internal/text/text.go

View workflow job for this annotation

GitHub Actions / lint

error-format: fmt.Errorf can be replaced with errors.New (perfsprint)
}

var b strings.Builder
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
if field.PkgPath != "" {
continue
}
tag := field.Tag.Get("text")
if tag == "-" {
continue
}
if tag == "" {
return "", fmt.Errorf("field %s missing required plain tag", field.Name)
}
val := rv.Field(i).Interface()
if field.Type.Kind() == reflect.Struct {
s, err := marshal(val, level+1)
if err != nil {
return "", err
}
fmt.Fprintf(&b, "%s:\n%s", tag, s)
} else {
indent := strings.Repeat(" ", level)
fmt.Fprintf(&b, "%s%s: %v\n", indent, tag, val)
}
}
return b.String(), nil
}
88 changes: 88 additions & 0 deletions internal/version/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package version

import (
"errors"
"os"
"runtime/debug"
"strconv"
"strings"
)

type Info struct {
Build Build `json:"build" text:"Build"`
Runtime Runtime `json:"runtime" text:"Runtime"`
}

type Build struct {
GoVersion string `json:"goVersion" text:"Go Version"`
Version string `json:"version" text:"Version"`
Commit string `json:"commit" text:"Commit"`
}

type Runtime struct {
Distro string `json:"distro" text:"Distribution"`
OS string `json:"os" text:"OS"`
Arch string `json:"arch" text:"Architecture"`
}

func (i Info) String() string {
return ""
}

func Load() (Info, error) {
bi, ok := debug.ReadBuildInfo()
if !ok {
return Info{}, errors.New("could not read build info")
}
info := Info{
Build: Build{
GoVersion: bi.GoVersion,
},
Runtime: Runtime{
Distro: getDistro(),
},
}
for _, s := range bi.Settings {
switch s.Key {
case "GOARCH":
info.Runtime.Arch = s.Value
case "GOOS":
info.Runtime.OS = s.Value
case "vcs.revision":
info.Build.Commit = s.Value
case "vcs.modified":
modified, err := strconv.ParseBool(s.Value)
if err != nil {
return Info{}, err
}
info.Build.Version = getVersion(bi.Main.Version, modified)
}
}
return info, nil
}

func getDistro() string {
unknownDistro := "unknown"
b, err := os.ReadFile("/etc/os-release")
if err != nil {
return unknownDistro
}
for line := range strings.SplitSeq(string(b), "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "PRETTY_NAME=") {
return strings.Trim(line[len("PRETTY_NAME="):], `"`)
}
}
return unknownDistro
}

func getVersion(mainVersion string, modified bool) string {
develVersion := "devel"
if modified {
return develVersion
}
if mainVersion == "" {
return develVersion
}
return mainVersion
}
44 changes: 40 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
Expand All @@ -22,6 +23,8 @@
"golang.org/x/sync/errgroup"

"github.com/spegel-org/spegel/internal/cleanup"
"github.com/spegel-org/spegel/internal/text"
"github.com/spegel-org/spegel/internal/version"
"github.com/spegel-org/spegel/pkg/metrics"
"github.com/spegel-org/spegel/pkg/oci"
"github.com/spegel-org/spegel/pkg/registry"
Expand All @@ -30,6 +33,10 @@
"github.com/spegel-org/spegel/pkg/web"
)

type VersionCmd struct {
Format string `arg:"--format" default:"text" help:"Format to output version information in."`
}

type ConfigurationCmd struct {
ContainerdRegistryConfigPath string `arg:"--containerd-registry-config-path,env:CONTAINERD_REGISTRY_CONFIG_PATH" default:"/etc/containerd/certs.d" help:"Directory where mirror configuration is written."`
MirroredRegistries []string `arg:"--mirrored-registries,env:MIRRORED_REGISTRIES" help:"Registries that are configured to be mirrored, if slice is empty all registires are mirrored."`
Expand Down Expand Up @@ -75,6 +82,7 @@
}

type Arguments struct {
Version *VersionCmd `arg:"subcommand:version"`
Configuration *ConfigurationCmd `arg:"subcommand:configuration"`
Registry *RegistryCmd `arg:"subcommand:registry"`
Cleanup *CleanupCmd `arg:"subcommand:cleanup"`
Expand All @@ -86,26 +94,30 @@
args := &Arguments{}
arg.MustParse(args)

ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM)
defer cancel()
opts := slog.HandlerOptions{
AddSource: true,
Level: args.LogLevel,
}
handler := slog.NewJSONHandler(os.Stderr, &opts)
log := logr.FromSlogHandler(handler)
ctx := logr.NewContext(context.Background(), log)
ctx = logr.NewContext(ctx, log)

err := run(ctx, args)
if err != nil {
log.Error(err, "run exit with error")
os.Exit(1)

Check failure on line 110 in main.go

View workflow job for this annotation

GitHub Actions / lint

exitAfterDefer: os.Exit will exit, and `defer cancel()` will not run (gocritic)
}
log.Info("gracefully shutdown")
if args.Version == nil {
log.Info("gracefully shutdown")
}
}

func run(ctx context.Context, args *Arguments) error {
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGTERM)
defer cancel()
switch {
case args.Version != nil:
return versionCommand(ctx, args.Version)
case args.Configuration != nil:
return configurationCommand(ctx, args.Configuration)
case args.Registry != nil:
Expand All @@ -119,6 +131,30 @@
}
}

func versionCommand(_ context.Context, args *VersionCmd) error {
info, err := version.Load()
if err != nil {
return err
}
switch args.Format {
case "text":
s, err := text.Marshal(&info)
if err != nil {
return err
}
fmt.Print(s)

Check failure on line 145 in main.go

View workflow job for this annotation

GitHub Actions / lint

use of `fmt.Print` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)
case "json":
b, err := json.Marshal(&info)
if err != nil {
return err
}
fmt.Print(string(b))

Check failure on line 151 in main.go

View workflow job for this annotation

GitHub Actions / lint

use of `fmt.Print` forbidden by pattern `^(fmt\.Print(|f|ln)|print|println)$` (forbidigo)
default:
return fmt.Errorf("unknown output format %s", args.Format)
}
return nil
}

func configurationCommand(ctx context.Context, args *ConfigurationCmd) error {
username, password, err := loadBasicAuth()
if err != nil {
Expand Down
Loading