diff --git a/cmd/scratchbox/generate_config.go b/cmd/scratchbox/generate_config.go new file mode 100644 index 0000000..0922513 --- /dev/null +++ b/cmd/scratchbox/generate_config.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" + + "scratchbox/internal/config" +) + +func newGenerateConfigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "generate-config", + Short: "Print a default config YAML to stdout", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + return writeDefaultConfig(cmd) + }, + } + return cmd +} + +func writeDefaultConfig(cmd *cobra.Command) error { + cfg := config.Config{ + Server: config.ServerConfig{ + ListenAddr: ":8080", + }, + Limits: config.LimitsConfig{ + MaxUploadSize: "100MB", + DefaultTTL: "1h", + }, + Storage: config.StorageConfig{ + DataDir: "./data", + CleanupInterval: "1m", + }, + Security: config.SecurityConfig{ + AllowedIPs: []string{}, + TrustProxyHeaders: false, + RateLimit: config.RateLimitConfig{ + Enabled: true, + RequestsPerMinute: 30, + Burst: 10, + }, + }, + } + + b, err := yaml.Marshal(cfg) + if err != nil { + return fmt.Errorf("marshal default config: %w", err) + } + + if _, err := cmd.OutOrStdout().Write(b); err != nil { + return fmt.Errorf("write default config: %w", err) + } + + return nil +} diff --git a/cmd/scratchbox/main.go b/cmd/scratchbox/main.go index bac38e7..a1c51fa 100644 --- a/cmd/scratchbox/main.go +++ b/cmd/scratchbox/main.go @@ -26,5 +26,6 @@ func newRootCmd() *cobra.Command { }, } cmd.AddCommand(newServerCmd()) + cmd.AddCommand(newGenerateConfigCmd()) return cmd } diff --git a/cmd/scratchbox/main_test.go b/cmd/scratchbox/main_test.go index 24aea9f..fe1a573 100644 --- a/cmd/scratchbox/main_test.go +++ b/cmd/scratchbox/main_test.go @@ -19,6 +19,7 @@ import ( "scratchbox/internal/config" httpapi "scratchbox/internal/http" "scratchbox/internal/storage" + "gopkg.in/yaml.v3" ) func repoRoot(t *testing.T) string { @@ -205,6 +206,55 @@ func TestMainHelperProcess(t *testing.T) { } } +func TestGenerateConfigCommandWritesDefaultYAML(t *testing.T) { + t.Parallel() + + cmd := newRootCmd() + var out bytes.Buffer + cmd.SetOut(&out) + cmd.SetErr(io.Discard) + cmd.SetArgs([]string{"generate-config"}) + + if err := cmd.Execute(); err != nil { + t.Fatalf("Execute() error = %v", err) + } + + if out.Len() == 0 { + t.Fatalf("generate-config output is empty") + } + + var cfg config.Config + if err := yaml.Unmarshal(out.Bytes(), &cfg); err != nil { + t.Fatalf("yaml.Unmarshal() error = %v", err) + } + + if cfg.Server.ListenAddr != ":8080" { + t.Fatalf("server.listen_addr = %q, want %q", cfg.Server.ListenAddr, ":8080") + } + if cfg.Limits.MaxUploadSize != "100MB" { + t.Fatalf("limits.max_upload_size = %q, want %q", cfg.Limits.MaxUploadSize, "100MB") + } + if cfg.Storage.DataDir != "./data" { + t.Fatalf("storage.data_dir = %q, want %q", cfg.Storage.DataDir, "./data") + } + if cfg.Security.RateLimit.RequestsPerMinute != 30 { + t.Fatalf("security.rate_limit.requests_per_minute = %d, want %d", cfg.Security.RateLimit.RequestsPerMinute, 30) + } +} + +func TestGenerateConfigCommandRejectsUnexpectedArgs(t *testing.T) { + t.Parallel() + + cmd := newRootCmd() + cmd.SetOut(io.Discard) + cmd.SetErr(io.Discard) + cmd.SetArgs([]string{"generate-config", "extra"}) + + if err := cmd.Execute(); err == nil { + t.Fatalf("expected error for unexpected args") + } +} + func TestLiveServerEndToEndHTTP(t *testing.T) { tmp := t.TempDir() cfgPath := filepath.Join(tmp, "config.yaml")