package main import ( "context" "errors" "flag" "fmt" "os" "path/filepath" "strings" "time" "github.com/easyai/easyai-ai-gateway/apps/api/internal/acceptancesnapshot" "github.com/jackc/pgx/v5/pgxpool" ) func main() { if len(os.Args) < 2 { usage() os.Exit(64) } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() var err error switch os.Args[1] { case "export": err = runExport(ctx, os.Args[2:]) case "validate": err = runValidate(os.Args[2:]) case "import": err = runImport(ctx, os.Args[2:]) default: usage() os.Exit(64) } if err != nil { fmt.Fprintln(os.Stderr, "acceptance snapshot:", err) os.Exit(1) } } func usage() { fmt.Fprintln(os.Stderr, `Usage: easyai-ai-gateway-acceptance-snapshot export --release-sha --output easyai-ai-gateway-acceptance-snapshot validate --input easyai-ai-gateway-acceptance-snapshot import --input --local-cluster-id export is read-only. import refuses to write unless system_settings contains a matching acceptance_local_cluster_id marker.`) } func runExport(ctx context.Context, args []string) error { flags := flag.NewFlagSet("export", flag.ContinueOnError) releaseSHA := flags.String("release-sha", "", "full source release SHA") output := flags.String("output", "", "secret-safe snapshot output path") if err := flags.Parse(args); err != nil { return err } if flags.NArg() != 0 { return errors.New("unexpected export arguments") } databaseURL := strings.TrimSpace(os.Getenv("AI_GATEWAY_DATABASE_URL")) if databaseURL == "" { return errors.New("AI_GATEWAY_DATABASE_URL is required") } outputPath, err := safeOutputPath(*output) if err != nil { return err } pool, err := pgxpool.New(ctx, databaseURL) if err != nil { return err } defer pool.Close() snapshot, err := acceptancesnapshot.Export(ctx, pool, acceptancesnapshot.ExportOptions{ ReleaseSHA: *releaseSHA, }) if err != nil { return err } payload, err := acceptancesnapshot.Encode(snapshot) if err != nil { return err } if err := os.WriteFile(outputPath, payload, 0o600); err != nil { return err } fmt.Printf( "acceptance_snapshot_export=PASS schema=%s config_hash=%s snapshot_sha256=%s\n", snapshot.SchemaVersion, snapshot.Source.ConfigHash, snapshot.SnapshotSHA256, ) return nil } func runValidate(args []string) error { flags := flag.NewFlagSet("validate", flag.ContinueOnError) input := flags.String("input", "", "snapshot input path") if err := flags.Parse(args); err != nil { return err } if flags.NArg() != 0 { return errors.New("unexpected validate arguments") } snapshot, err := readSnapshot(*input) if err != nil { return err } fmt.Printf( "acceptance_snapshot_validate=PASS schema=%s release=%s config_hash=%s snapshot_sha256=%s\n", snapshot.SchemaVersion, snapshot.Source.ReleaseSHA, snapshot.Source.ConfigHash, snapshot.SnapshotSHA256, ) return nil } func runImport(ctx context.Context, args []string) error { flags := flag.NewFlagSet("import", flag.ContinueOnError) input := flags.String("input", "", "snapshot input path") clusterID := flags.String("local-cluster-id", "", "required local cluster marker") if err := flags.Parse(args); err != nil { return err } if flags.NArg() != 0 { return errors.New("unexpected import arguments") } databaseURL := strings.TrimSpace(os.Getenv("AI_GATEWAY_DATABASE_URL")) if databaseURL == "" { return errors.New("AI_GATEWAY_DATABASE_URL is required") } snapshot, err := readSnapshot(*input) if err != nil { return err } pool, err := pgxpool.New(ctx, databaseURL) if err != nil { return err } defer pool.Close() if err := acceptancesnapshot.Import(ctx, pool, snapshot, *clusterID); err != nil { return err } fmt.Printf( "acceptance_snapshot_import=PASS cluster_id=%s config_hash=%s snapshot_sha256=%s\n", strings.TrimSpace(*clusterID), snapshot.Source.ConfigHash, snapshot.SnapshotSHA256, ) return nil } func readSnapshot(path string) (acceptancesnapshot.Snapshot, error) { path = strings.TrimSpace(path) if path == "" { return acceptancesnapshot.Snapshot{}, errors.New("snapshot path is required") } info, err := os.Lstat(path) if err != nil { return acceptancesnapshot.Snapshot{}, err } if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 { return acceptancesnapshot.Snapshot{}, errors.New("snapshot must be a regular non-symlink file") } payload, err := os.ReadFile(path) if err != nil { return acceptancesnapshot.Snapshot{}, err } return acceptancesnapshot.Decode(payload) } func safeOutputPath(path string) (string, error) { path = strings.TrimSpace(path) if path == "" { return "", errors.New("output path is required") } absolute, err := filepath.Abs(path) if err != nil { return "", err } parent := filepath.Dir(absolute) if err := os.MkdirAll(parent, 0o700); err != nil { return "", err } if info, err := os.Lstat(absolute); err == nil { if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 { return "", errors.New("output must be a regular non-symlink file") } } else if !os.IsNotExist(err) { return "", err } return absolute, nil }