Add initial distributor CLI skeleton

This commit is contained in:
2026-05-31 01:47:39 +00:00
parent 0818733b19
commit 22d0424232
19 changed files with 436 additions and 2 deletions

14
internal/app/app.go Normal file
View File

@@ -0,0 +1,14 @@
package app
import "errors"
const Name = "distributor"
// Version can be replaced at build time with -ldflags "-X .../internal/app.Version=<value>".
var Version = "dev"
var ErrNotImplemented = errors.New("not implemented")
func VersionString() string {
return Name + " " + Version
}

16
internal/app/app_test.go Normal file
View File

@@ -0,0 +1,16 @@
package app
import "testing"
func TestVersionString(t *testing.T) {
oldVersion := Version
t.Cleanup(func() {
Version = oldVersion
})
Version = "1.2.3"
if got, want := VersionString(), "distributor 1.2.3"; got != want {
t.Fatalf("VersionString() = %q, want %q", got, want)
}
}

14
internal/app/inspect.go Normal file
View File

@@ -0,0 +1,14 @@
package app
import (
"context"
"fmt"
)
type InspectOptions struct {
Path string
}
func Inspect(context.Context, InspectOptions) error {
return fmt.Errorf("inspect command: %w", ErrNotImplemented)
}

5
internal/app/pipeline.go Normal file
View File

@@ -0,0 +1,5 @@
package app
type Pipeline struct {
ID string
}

12
internal/app/run.go Normal file
View File

@@ -0,0 +1,12 @@
package app
import (
"context"
"fmt"
)
type RunOptions struct{}
func Run(context.Context, RunOptions) error {
return fmt.Errorf("run command: %w", ErrNotImplemented)
}

14
internal/app/validate.go Normal file
View File

@@ -0,0 +1,14 @@
package app
import (
"context"
"fmt"
)
type ValidateOptions struct {
Path string
}
func Validate(context.Context, ValidateOptions) error {
return fmt.Errorf("validate command: %w", ErrNotImplemented)
}

36
internal/cli/inspect.go Normal file
View File

@@ -0,0 +1,36 @@
package cli
import (
"context"
"fmt"
"io"
"gitea.maximumdirect.net/eric/distributor/internal/app"
)
func inspectCommand(ctx context.Context, args []string, stdout, stderr io.Writer) int {
if hasHelp(args) {
printInspectHelp(stdout)
return exitOK
}
if len(args) > 1 {
fmt.Fprintf(stderr, "%s: inspect accepts at most one path\n", app.Name)
return exitUsage
}
var path string
if len(args) == 1 {
path = args[0]
}
if err := app.Inspect(ctx, app.InspectOptions{Path: path}); err != nil {
return fail(stderr, err)
}
return exitOK
}
func printInspectHelp(w io.Writer) {
fmt.Fprint(w, `Usage:
distributor inspect <path>
The inspect command is present but bundle inspection is not implemented yet.
`)
}

84
internal/cli/root.go Normal file
View File

@@ -0,0 +1,84 @@
package cli
import (
"context"
"errors"
"fmt"
"io"
"strings"
"gitea.maximumdirect.net/eric/distributor/internal/app"
)
const (
exitOK = 0
exitError = 1
exitUsage = 2
)
func Execute(ctx context.Context, args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
printRootHelp(stdout)
return exitOK
}
switch args[0] {
case "-h", "--help", "help":
printRootHelp(stdout)
return exitOK
case "version":
return versionCommand(ctx, args[1:], stdout, stderr)
case "run":
return runCommand(ctx, args[1:], stdout, stderr)
case "validate":
return validateCommand(ctx, args[1:], stdout, stderr)
case "inspect":
return inspectCommand(ctx, args[1:], stdout, stderr)
default:
fmt.Fprintf(stderr, "%s: unknown command %q\n\n", app.Name, args[0])
printRootHelp(stderr)
return exitUsage
}
}
func printRootHelp(w io.Writer) {
fmt.Fprintf(w, `%s validates and publishes manifested report bundles.
Usage:
%s <command> [options]
Commands:
version Print version information
run Run configured distribution pipelines
validate Validate a source bundle or bundle tree
inspect Inspect bundles or distributor state
Use "%s <command> --help" for command-specific help.
`, app.Name, app.Name, app.Name)
}
func hasHelp(args []string) bool {
for _, arg := range args {
if arg == "-h" || arg == "--help" {
return true
}
}
return false
}
func fail(stderr io.Writer, err error) int {
if errors.Is(err, app.ErrNotImplemented) {
fmt.Fprintf(stderr, "%s: %s\n", app.Name, err)
return exitError
}
fmt.Fprintf(stderr, "%s: %s\n", app.Name, err)
return exitError
}
func rejectExtraArgs(stderr io.Writer, command string, args []string) bool {
if len(args) == 0 {
return false
}
fmt.Fprintf(stderr, "%s: %s does not accept arguments: %s\n", app.Name, command, strings.Join(args, " "))
return true
}

75
internal/cli/root_test.go Normal file
View File

@@ -0,0 +1,75 @@
package cli
import (
"bytes"
"context"
"strings"
"testing"
)
func TestExecuteRootHelp(t *testing.T) {
var stdout, stderr bytes.Buffer
code := Execute(context.Background(), []string{"--help"}, &stdout, &stderr)
if code != exitOK {
t.Fatalf("exit code = %d, want %d", code, exitOK)
}
if !strings.Contains(stdout.String(), "Usage:") {
t.Fatalf("stdout = %q, want help text", stdout.String())
}
if stderr.Len() != 0 {
t.Fatalf("stderr = %q, want empty", stderr.String())
}
}
func TestExecuteVersion(t *testing.T) {
var stdout, stderr bytes.Buffer
code := Execute(context.Background(), []string{"version"}, &stdout, &stderr)
if code != exitOK {
t.Fatalf("exit code = %d, want %d", code, exitOK)
}
if got, want := stdout.String(), "distributor dev\n"; got != want {
t.Fatalf("stdout = %q, want %q", got, want)
}
if stderr.Len() != 0 {
t.Fatalf("stderr = %q, want empty", stderr.String())
}
}
func TestPlaceholderCommandsFailClearly(t *testing.T) {
tests := []string{"run", "validate", "inspect"}
for _, command := range tests {
t.Run(command, func(t *testing.T) {
var stdout, stderr bytes.Buffer
code := Execute(context.Background(), []string{command}, &stdout, &stderr)
if code != exitError {
t.Fatalf("exit code = %d, want %d", code, exitError)
}
if stdout.Len() != 0 {
t.Fatalf("stdout = %q, want empty", stdout.String())
}
if !strings.Contains(stderr.String(), "not implemented") {
t.Fatalf("stderr = %q, want not implemented error", stderr.String())
}
})
}
}
func TestUnknownCommandIsUsageError(t *testing.T) {
var stdout, stderr bytes.Buffer
code := Execute(context.Background(), []string{"nope"}, &stdout, &stderr)
if code != exitUsage {
t.Fatalf("exit code = %d, want %d", code, exitUsage)
}
if !strings.Contains(stderr.String(), "unknown command") {
t.Fatalf("stderr = %q, want unknown command error", stderr.String())
}
}

32
internal/cli/run.go Normal file
View File

@@ -0,0 +1,32 @@
package cli
import (
"context"
"fmt"
"io"
"gitea.maximumdirect.net/eric/distributor/internal/app"
)
func runCommand(ctx context.Context, args []string, stdout, stderr io.Writer) int {
if hasHelp(args) {
printRunHelp(stdout)
return exitOK
}
if len(args) > 0 {
fmt.Fprintf(stderr, "%s: run does not accept options yet: %v\n", app.Name, args)
return exitUsage
}
if err := app.Run(ctx, app.RunOptions{}); err != nil {
return fail(stderr, err)
}
return exitOK
}
func printRunHelp(w io.Writer) {
fmt.Fprint(w, `Usage:
distributor run
The run command is present but distribution behavior is not implemented yet.
`)
}

36
internal/cli/validate.go Normal file
View File

@@ -0,0 +1,36 @@
package cli
import (
"context"
"fmt"
"io"
"gitea.maximumdirect.net/eric/distributor/internal/app"
)
func validateCommand(ctx context.Context, args []string, stdout, stderr io.Writer) int {
if hasHelp(args) {
printValidateHelp(stdout)
return exitOK
}
if len(args) > 1 {
fmt.Fprintf(stderr, "%s: validate accepts at most one path\n", app.Name)
return exitUsage
}
var path string
if len(args) == 1 {
path = args[0]
}
if err := app.Validate(ctx, app.ValidateOptions{Path: path}); err != nil {
return fail(stderr, err)
}
return exitOK
}
func printValidateHelp(w io.Writer) {
fmt.Fprint(w, `Usage:
distributor validate <path>
The validate command is present but bundle validation is not implemented yet.
`)
}

29
internal/cli/version.go Normal file
View File

@@ -0,0 +1,29 @@
package cli
import (
"context"
"fmt"
"io"
"gitea.maximumdirect.net/eric/distributor/internal/app"
)
func versionCommand(_ context.Context, args []string, stdout, stderr io.Writer) int {
if hasHelp(args) {
printVersionHelp(stdout)
return exitOK
}
if rejectExtraArgs(stderr, "version", args) {
return exitUsage
}
fmt.Fprintln(stdout, app.VersionString())
return exitOK
}
func printVersionHelp(w io.Writer) {
fmt.Fprint(w, `Usage:
distributor version
Print version information.
`)
}

View File

@@ -0,0 +1,3 @@
package config
type Config struct{}

View File

@@ -0,0 +1,3 @@
package config
const DefaultConfigPath = "/usr/local/etc/distributor/config.yml"

View File

@@ -0,0 +1,7 @@
package logging
import "io"
func Configure(io.Writer) error {
return nil
}