Add configured source validation and inspection
This commit is contained in:
@@ -16,6 +16,9 @@ func inspectCommand(ctx context.Context, args []string, stdout, stderr io.Writer
|
||||
}
|
||||
flags := flag.NewFlagSet("inspect", flag.ContinueOnError)
|
||||
flags.SetOutput(stderr)
|
||||
configPath := flags.String("config", "", "path to config file")
|
||||
pipelineID := flags.String("pipeline", "", "pipeline id")
|
||||
bundlePath := flags.String("bundle", "", "source-root-relative bundle path")
|
||||
formatFlag := addFormatFlag(flags)
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return exitUsage
|
||||
@@ -28,7 +31,17 @@ func inspectCommand(ctx context.Context, args []string, stdout, stderr io.Writer
|
||||
if !ok {
|
||||
return exitUsage
|
||||
}
|
||||
if err := app.Inspect(ctx, app.InspectOptions{Path: path, Stdout: stdout, OutputFormat: format}); err != nil {
|
||||
if !validateInspectModeOK(stderr, "inspect", path, *configPath, *pipelineID, *bundlePath) {
|
||||
return exitUsage
|
||||
}
|
||||
if err := app.Inspect(ctx, app.InspectOptions{
|
||||
Path: path,
|
||||
ConfigPath: *configPath,
|
||||
PipelineID: *pipelineID,
|
||||
BundlePath: *bundlePath,
|
||||
Stdout: stdout,
|
||||
OutputFormat: format,
|
||||
}); err != nil {
|
||||
return fail(stderr, err)
|
||||
}
|
||||
return exitOK
|
||||
@@ -37,10 +50,15 @@ func inspectCommand(ctx context.Context, args []string, stdout, stderr io.Writer
|
||||
func printInspectHelp(w io.Writer) {
|
||||
fmt.Fprint(w, `Usage:
|
||||
distributor inspect [--format text|json] <path>
|
||||
distributor inspect --config <path> --pipeline <id> [--bundle <path>] [--format text|json]
|
||||
|
||||
Options:
|
||||
--config <path> Path to config file for configured source inspection
|
||||
--pipeline <id> Pipeline id to inspect in config mode
|
||||
--bundle <path> Source-root-relative bundle path to inspect
|
||||
--format text|json Output format
|
||||
|
||||
Print a normalized summary of local source bundles.
|
||||
Print a normalized summary of local source bundles or a configured pipeline
|
||||
source.
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -139,6 +139,45 @@ func TestExecuteValidateJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteValidateConfiguredSource(t *testing.T) {
|
||||
sourceRoot := t.TempDir()
|
||||
destinationRoot := t.TempDir()
|
||||
testutil.WriteSourceBundle(t, sourceRoot, "", testutil.BundleOptions{})
|
||||
configPath := testutil.WriteMinimalLocalConfig(t, sourceRoot, destinationRoot)
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
code := Execute(context.Background(), []string{"validate", "--config", configPath, "--pipeline", "reports"}, &stdout, &stderr)
|
||||
|
||||
if code != exitOK {
|
||||
t.Fatalf("exit code = %d, want %d; stderr = %q", code, exitOK, stderr.String())
|
||||
}
|
||||
if got, want := stdout.String(), "Validated 1 bundle(s) for pipeline reports source local\n"; got != want {
|
||||
t.Fatalf("stdout = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteValidateConfiguredSourceJSON(t *testing.T) {
|
||||
sourceRoot := t.TempDir()
|
||||
destinationRoot := t.TempDir()
|
||||
testutil.WriteSourceBundle(t, sourceRoot, "daily", testutil.BundleOptions{ID: "reports.daily"})
|
||||
configPath := testutil.WriteMinimalLocalConfig(t, sourceRoot, destinationRoot)
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
code := Execute(context.Background(), []string{"validate", "--config", configPath, "--pipeline", "reports", "--bundle", "daily", "--format", "json"}, &stdout, &stderr)
|
||||
|
||||
if code != exitOK {
|
||||
t.Fatalf("exit code = %d, want %d; stderr = %q", code, exitOK, stderr.String())
|
||||
}
|
||||
envelope := decodeEnvelope(t, &stdout)
|
||||
if envelope["command"] != "validate" || envelope["ok"] != true {
|
||||
t.Fatalf("envelope = %#v, want validate ok", envelope)
|
||||
}
|
||||
result := envelopeResult(t, envelope)
|
||||
if result["pipeline_id"] != "reports" || result["source_backend"] != "local" || result["bundle_count"] != float64(1) {
|
||||
t.Fatalf("result = %#v, want configured source metadata", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteValidateArgs(t *testing.T) {
|
||||
validPath := filepath.Join("..", "bundle", "testdata", "valid_bundle")
|
||||
tests := []struct {
|
||||
@@ -166,6 +205,24 @@ func TestExecuteValidateArgs(t *testing.T) {
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "accepts at most one path",
|
||||
},
|
||||
{
|
||||
name: "path plus config",
|
||||
args: []string{"validate", "--config", "config.yml", "--pipeline", "reports", validPath},
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "does not accept a local path",
|
||||
},
|
||||
{
|
||||
name: "pipeline without config",
|
||||
args: []string{"validate", "--pipeline", "reports"},
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "requires --config",
|
||||
},
|
||||
{
|
||||
name: "config without pipeline",
|
||||
args: []string{"validate", "--config", "config.yml"},
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "requires --pipeline",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -223,6 +280,30 @@ func TestExecuteInspectJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteInspectConfiguredSource(t *testing.T) {
|
||||
sourceRoot := t.TempDir()
|
||||
destinationRoot := t.TempDir()
|
||||
testutil.WriteSourceBundle(t, sourceRoot, "daily", testutil.BundleOptions{ID: "reports.daily"})
|
||||
configPath := testutil.WriteMinimalLocalConfig(t, sourceRoot, destinationRoot)
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
code := Execute(context.Background(), []string{"inspect", "--config", configPath, "--pipeline", "reports"}, &stdout, &stderr)
|
||||
|
||||
if code != exitOK {
|
||||
t.Fatalf("exit code = %d, want %d; stderr = %q", code, exitOK, stderr.String())
|
||||
}
|
||||
for _, want := range []string{
|
||||
"Pipeline: reports",
|
||||
"Source: local",
|
||||
"path=daily",
|
||||
"id=reports.daily",
|
||||
} {
|
||||
if !strings.Contains(stdout.String(), want) {
|
||||
t.Fatalf("stdout = %q, want substring %q", stdout.String(), want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteInspectArgs(t *testing.T) {
|
||||
validPath := filepath.Join("..", "bundle", "testdata", "valid_bundle")
|
||||
tests := []struct {
|
||||
@@ -250,6 +331,24 @@ func TestExecuteInspectArgs(t *testing.T) {
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "accepts at most one path",
|
||||
},
|
||||
{
|
||||
name: "path plus config",
|
||||
args: []string{"inspect", "--config", "config.yml", "--pipeline", "reports", validPath},
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "does not accept a local path",
|
||||
},
|
||||
{
|
||||
name: "pipeline without config",
|
||||
args: []string{"inspect", "--pipeline", "reports"},
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "requires --config",
|
||||
},
|
||||
{
|
||||
name: "config without pipeline",
|
||||
args: []string{"inspect", "--config", "config.yml"},
|
||||
wantCode: exitUsage,
|
||||
wantStderr: "requires --pipeline",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
26
internal/cli/source_mode.go
Normal file
26
internal/cli/source_mode.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func validateInspectModeOK(stderr io.Writer, command, path, configPath, pipelineID, bundlePath string) bool {
|
||||
configMode := configPath != "" || pipelineID != "" || bundlePath != ""
|
||||
if !configMode {
|
||||
return true
|
||||
}
|
||||
if path != "" {
|
||||
fmt.Fprintf(stderr, "distributor: %s does not accept a local path with --config, --pipeline, or --bundle\n", command)
|
||||
return false
|
||||
}
|
||||
if configPath == "" {
|
||||
fmt.Fprintf(stderr, "distributor: %s requires --config when --pipeline or --bundle is set\n", command)
|
||||
return false
|
||||
}
|
||||
if pipelineID == "" {
|
||||
fmt.Fprintf(stderr, "distributor: %s requires --pipeline in config mode\n", command)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -16,6 +16,9 @@ func validateCommand(ctx context.Context, args []string, stdout, stderr io.Write
|
||||
}
|
||||
flags := flag.NewFlagSet("validate", flag.ContinueOnError)
|
||||
flags.SetOutput(stderr)
|
||||
configPath := flags.String("config", "", "path to config file")
|
||||
pipelineID := flags.String("pipeline", "", "pipeline id")
|
||||
bundlePath := flags.String("bundle", "", "source-root-relative bundle path")
|
||||
formatFlag := addFormatFlag(flags)
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return exitUsage
|
||||
@@ -28,7 +31,17 @@ func validateCommand(ctx context.Context, args []string, stdout, stderr io.Write
|
||||
if !ok {
|
||||
return exitUsage
|
||||
}
|
||||
if err := app.Validate(ctx, app.ValidateOptions{Path: path, Stdout: stdout, OutputFormat: format}); err != nil {
|
||||
if !validateInspectModeOK(stderr, "validate", path, *configPath, *pipelineID, *bundlePath) {
|
||||
return exitUsage
|
||||
}
|
||||
if err := app.Validate(ctx, app.ValidateOptions{
|
||||
Path: path,
|
||||
ConfigPath: *configPath,
|
||||
PipelineID: *pipelineID,
|
||||
BundlePath: *bundlePath,
|
||||
Stdout: stdout,
|
||||
OutputFormat: format,
|
||||
}); err != nil {
|
||||
return fail(stderr, err)
|
||||
}
|
||||
return exitOK
|
||||
@@ -37,10 +50,15 @@ func validateCommand(ctx context.Context, args []string, stdout, stderr io.Write
|
||||
func printValidateHelp(w io.Writer) {
|
||||
fmt.Fprint(w, `Usage:
|
||||
distributor validate [--format text|json] <path>
|
||||
distributor validate --config <path> --pipeline <id> [--bundle <path>] [--format text|json]
|
||||
|
||||
Options:
|
||||
--config <path> Path to config file for configured source validation
|
||||
--pipeline <id> Pipeline id to validate in config mode
|
||||
--bundle <path> Source-root-relative bundle path to validate
|
||||
--format text|json Output format
|
||||
|
||||
Validate a local source bundle directory or a tree containing source bundles.
|
||||
Validate a local source bundle directory, a local source bundle tree, or a
|
||||
configured pipeline source.
|
||||
`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user