Add initial distributor CLI skeleton
This commit is contained in:
14
internal/app/app.go
Normal file
14
internal/app/app.go
Normal 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
16
internal/app/app_test.go
Normal 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
14
internal/app/inspect.go
Normal 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
5
internal/app/pipeline.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package app
|
||||
|
||||
type Pipeline struct {
|
||||
ID string
|
||||
}
|
||||
12
internal/app/run.go
Normal file
12
internal/app/run.go
Normal 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
14
internal/app/validate.go
Normal 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
36
internal/cli/inspect.go
Normal 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
84
internal/cli/root.go
Normal 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
75
internal/cli/root_test.go
Normal 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
32
internal/cli/run.go
Normal 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
36
internal/cli/validate.go
Normal 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
29
internal/cli/version.go
Normal 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.
|
||||
`)
|
||||
}
|
||||
3
internal/config/config.go
Normal file
3
internal/config/config.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package config
|
||||
|
||||
type Config struct{}
|
||||
3
internal/config/defaults.go
Normal file
3
internal/config/defaults.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package config
|
||||
|
||||
const DefaultConfigPath = "/usr/local/etc/distributor/config.yml"
|
||||
7
internal/logging/logging.go
Normal file
7
internal/logging/logging.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package logging
|
||||
|
||||
import "io"
|
||||
|
||||
func Configure(io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user