package main import ( "context" "encoding/json" "flag" "fmt" "os" "path/filepath" "strings" "time" "scout/pkg/client" "scout/pkg/scoutpkg" "scout/pkg/updater" ) type targetFlags struct { platform *string architecture *string channel *string } type managerFlags struct { server *string packageName *string rootDir *string currentVersion *string currentPath *string entrypoint *string platform *string architecture *string channel *string healthTimeout *time.Duration keepReleases *int } func checkCommand(args []string) { flags := flag.NewFlagSet("check", flag.ExitOnError) server := flags.String("server", "http://localhost:8080", "\u670d\u52a1\u7aef\u5730\u5740") name := flags.String("package", "", "\u5305\u540d") current := flags.String("current", "", "\u5f53\u524d\u7248\u672c") target := bindTargetFlags(flags) flags.Parse(args) requirePackage(*name) api := newClient(*server, "") latest, available, err := api.CheckUpdateFor(context.Background(), *name, *current, target.options(*current)) if err != nil { fatal("\u68c0\u67e5\u66f4\u65b0\u5931\u8d25: %v", err) } if available { fmt.Printf("\u53d1\u73b0\u66f4\u65b0\uff1a%s %s -> %s\uff08%s/%s\uff09\n", latest.Name, *current, latest.Version, latest.Platform, latest.Architecture) return } fmt.Printf("\u5f53\u524d\u5df2\u662f\u6700\u65b0\u7248\u672c\uff1a%s\n", latest.Version) } func downloadCommand(args []string) { flags := flag.NewFlagSet("download", flag.ExitOnError) server := flags.String("server", "http://localhost:8080", "\u670d\u52a1\u7aef\u5730\u5740") name := flags.String("package", "", "\u5305\u540d") version := flags.String("version", "", "\u7248\u672c\uff1b\u4e3a\u7a7a\u65f6\u4e0b\u8f7d\u6700\u65b0\u7248\u672c") current := flags.String("current", "", "\u5f53\u524d\u7248\u672c") output := flags.String("output", "", "\u4fdd\u5b58\u8def\u5f84") target := bindTargetFlags(flags) flags.Parse(args) requirePackage(*name) if *output == "" { resolvedVersion := *version if resolvedVersion == "" { resolvedVersion = "latest" } *output = filepath.Join(".", fmt.Sprintf("%s-%s.pkg", *name, resolvedVersion)) } api := newClient(*server, "") item, err := api.DownloadFor(context.Background(), *name, *version, *output, target.options(*current)) if err != nil { fatal("\u4e0b\u8f7d\u5931\u8d25: %v", err) } fmt.Printf("\u4e0b\u8f7d\u5b8c\u6210\uff1a%s\n\u7248\u672c\uff1a%s\n\u76ee\u6807\uff1a%s/%s\nSHA-256\uff1a%s\n", *output, item.Version, item.Platform, item.Architecture, item.SHA256) } func uploadCommand(args []string) { flags := flag.NewFlagSet("upload", flag.ExitOnError) server := flags.String("server", "http://localhost:8080", "\u670d\u52a1\u7aef\u5730\u5740") token := flags.String("token", "", "\u5f00\u53d1\u8005\u4ee4\u724c") name := flags.String("package", "", "\u5305\u540d\uff1bScout \u5305\u53ef\u7701\u7565") version := flags.String("version", "", "\u7248\u672c\uff1bScout \u5305\u53ef\u7701\u7565") filePath := flags.String("file", "", "\u5305\u6587\u4ef6\u8def\u5f84") description := flags.String("description", "", "\u5305\u63cf\u8ff0") packageType := flags.String("package-type", "full", "\u5305\u7c7b\u578b\uff1afull \u6216 delta") channel := flags.String("channel", "stable", "\u53d1\u5e03\u901a\u9053\uff1astable\u3001beta \u6216 dev") status := flags.String("status", "published", "\u53d1\u5e03\u72b6\u6001") upgradeFrom := flags.String("upgrade-from", "", "\u5dee\u5206\u5305\u6765\u6e90\u7248\u672c\uff0c\u591a\u4e2a\u7528\u9017\u53f7\u5206\u9694") target := bindTargetFlags(flags) flags.Parse(args) if *filePath == "" { fatal("\u5fc5\u987b\u6307\u5b9a --file") } isScout := strings.EqualFold(filepath.Ext(*filePath), ".scout") if !isScout && (*name == "" || *version == "" || *target.platform == "" || *target.architecture == "") { fatal("\u666e\u901a\u5305\u5fc5\u987b\u6307\u5b9a --package\u3001--version\u3001--platform \u548c --architecture") } file, err := os.Open(*filePath) if err != nil { fatal("\u6253\u5f00\u5305\u6587\u4ef6\u5931\u8d25: %v", err) } defer file.Close() api := newClient(*server, *token) item, err := api.Upload(context.Background(), client.UploadRequest{ Name: *name, Version: *version, Description: *description, Platform: *target.platform, Architecture: *target.architecture, PackageType: *packageType, Channel: *channel, Status: *status, UpgradeFrom: splitPackages(*upgradeFrom), FileName: filepath.Base(*filePath), File: file, }) if err != nil { fatal("\u4e0a\u4f20\u5931\u8d25: %v", err) } fmt.Printf("\u4e0a\u4f20\u6210\u529f\uff1a%s %s\n\u901a\u9053\uff1a%s\n\u76ee\u6807\u6570\uff1a%d\nSHA-256\uff1a%s\n", item.Name, item.Version, item.Channel, len(item.Targets), item.SHA256) } func watchCommand(args []string) { flags := flag.NewFlagSet("watch", flag.ExitOnError) server := flags.String("server", "http://localhost:8080", "\u670d\u52a1\u7aef\u5730\u5740") packages := flags.String("packages", "", "\u8981\u76d1\u542c\u7684\u5305\u540d\uff0c\u591a\u4e2a\u5305\u7528\u9017\u53f7\u5206\u9694") versions := flags.String("versions", "", "\u5f53\u524d\u7248\u672c\uff0c\u683c\u5f0f\u4e3a package:version\uff0c\u591a\u4e2a\u7528\u9017\u53f7\u5206\u9694") target := bindTargetFlags(flags) flags.Parse(args) api := newClient(*server, "") updates, err := api.WatchUpdatesFor(context.Background(), client.WatchRequest{ Packages: splitPackages(*packages), Versions: parseVersions(*versions), Platform: *target.platform, Architecture: *target.architecture, Channel: *target.channel, }) if err != nil { fatal("\u8fde\u63a5\u66f4\u65b0\u901a\u77e5\u5931\u8d25: %v", err) } fmt.Println("\u5df2\u8fde\u63a5\u66f4\u65b0\u901a\u77e5\uff0c\u7b49\u5f85\u65b0\u5305\u2026\u2026") for update := range updates { fmt.Printf("\u6536\u5230\u66f4\u65b0\uff1a%s %s\uff08%s/%s\uff09\n", update.Package.Name, update.Package.Version, update.Package.Platform, update.Package.Architecture) } } func prepareCommand(args []string) { flags := flag.NewFlagSet("prepare", flag.ExitOnError) managerInput := bindManagerFlags(flags) version := flags.String("version", "", "\u8981\u51c6\u5907\u7684\u7248\u672c\uff1b\u4e3a\u7a7a\u65f6\u4f7f\u7528\u6700\u65b0\u7248\u672c") flags.Parse(args) manager := newUpdateManager(managerInput) prepared, err := manager.Prepare(context.Background(), *version) if err != nil { fatal("\u51c6\u5907\u66f4\u65b0\u5931\u8d25: %v", err) } fmt.Printf("\u66f4\u65b0\u5df2\u51c6\u5907\uff1a%s\n\u76ee\u5f55\uff1a%s\n\u5c1a\u672a\u5207\u6362\u5f53\u524d\u7a0b\u5e8f\uff0c\u53ef\u7531\u7528\u6237\u51b3\u5b9a\u4f55\u65f6\u91cd\u542f\u3002\n", prepared.Version, prepared.Path) } func statusCommand(args []string) { flags := flag.NewFlagSet("status", flag.ExitOnError) managerInput := bindManagerFlags(flags) flags.Parse(args) status, err := newUpdateManager(managerInput).Status() if err != nil { fatal("\u8bfb\u53d6\u66f4\u65b0\u72b6\u6001\u5931\u8d25: %v", err) } data, _ := json.MarshalIndent(status, "", " ") fmt.Println(string(data)) } func activateCommand(args []string) { flags := flag.NewFlagSet("activate", flag.ExitOnError) managerInput := bindManagerFlags(flags) restartNow := flags.Bool("restart-now", false, "\u6fc0\u6d3b\u540e\u4f7f\u7528\u7ea6\u5b9a\u9000\u51fa\u7801\u901a\u77e5\u5bbf\u4e3b\u7acb\u5373\u91cd\u542f") flags.Parse(args) activated, err := newUpdateManager(managerInput).Activate() if err != nil { fatal("\u6fc0\u6d3b\u66f4\u65b0\u5931\u8d25: %v", err) } fmt.Printf("\u5df2\u6fc0\u6d3b\u7248\u672c\uff1a%s\n", activated.Version) if *restartNow { os.Exit(updater.ExitRestartNow) } } func healthyCommand(args []string) { flags := flag.NewFlagSet("healthy", flag.ExitOnError) managerInput := bindManagerFlags(flags) version := flags.String("version", "", "\u8981\u786e\u8ba4\u7684\u5f53\u524d\u7248\u672c") flags.Parse(args) if err := newUpdateManager(managerInput).ConfirmHealthy(*version); err != nil { fatal("\u5065\u5eb7\u786e\u8ba4\u5931\u8d25: %v", err) } fmt.Println("\u65b0\u7248\u672c\u5df2\u901a\u8fc7\u5065\u5eb7\u786e\u8ba4\uff0c\u65e7\u7248\u672c\u5df2\u6309\u4fdd\u7559\u7b56\u7565\u6e05\u7406\u3002") } func rollbackCommand(args []string) { flags := flag.NewFlagSet("rollback", flag.ExitOnError) managerInput := bindManagerFlags(flags) reason := flags.String("reason", "\u7528\u6237\u624b\u52a8\u56de\u6eda", "\u56de\u6eda\u539f\u56e0") flags.Parse(args) rolledBack, err := newUpdateManager(managerInput).Rollback(fmt.Errorf("%s", *reason)) if err != nil { fatal("\u56de\u6eda\u5931\u8d25: %v", err) } fmt.Printf("\u5df2\u56de\u6eda\u5230\u7248\u672c\uff1a%s\n", rolledBack.Version) } func bindTargetFlags(flags *flag.FlagSet) targetFlags { defaultPlatform, defaultArchitecture := scoutpkg.CurrentTarget() return targetFlags{ platform: flags.String("platform", defaultPlatform, "\u8fd0\u884c\u5e73\u53f0"), architecture: flags.String("architecture", defaultArchitecture, "\u8fd0\u884c\u67b6\u6784"), channel: flags.String("channel", "stable", "\u53d1\u5e03\u901a\u9053"), } } func (target targetFlags) options(currentVersion string) client.QueryOptions { return client.QueryOptions{ Platform: *target.platform, Architecture: *target.architecture, Channel: *target.channel, CurrentVersion: currentVersion, } } func bindManagerFlags(flags *flag.FlagSet) managerFlags { defaultPlatform, defaultArchitecture := scoutpkg.CurrentTarget() return managerFlags{ server: flags.String("server", "http://localhost:8080", "\u670d\u52a1\u7aef\u5730\u5740"), packageName: flags.String("package", "", "\u5305\u540d"), rootDir: flags.String("root", ".scout-update", "\u66f4\u65b0\u5de5\u4f5c\u76ee\u5f55"), currentVersion: flags.String("current", "", "\u5f53\u524d\u7248\u672c"), currentPath: flags.String("current-path", "", "\u5f53\u524d\u7a0b\u5e8f\u76ee\u5f55"), entrypoint: flags.String("entrypoint", "", "\u7a0b\u5e8f\u5165\u53e3\u76f8\u5bf9\u8def\u5f84"), platform: flags.String("platform", defaultPlatform, "\u8fd0\u884c\u5e73\u53f0"), architecture: flags.String("architecture", defaultArchitecture, "\u8fd0\u884c\u67b6\u6784"), channel: flags.String("channel", "stable", "\u53d1\u5e03\u901a\u9053"), healthTimeout: flags.Duration("health-timeout", 2*time.Minute, "\u5065\u5eb7\u786e\u8ba4\u8d85\u65f6\u65f6\u95f4"), keepReleases: flags.Int("keep", 2, "\u5065\u5eb7\u786e\u8ba4\u540e\u4fdd\u7559\u7684\u7248\u672c\u6570"), } } func newUpdateManager(input managerFlags) *updater.Manager { requirePackage(*input.packageName) api := newClient(*input.server, "") manager, err := updater.New(updater.Config{ Client: api, PackageName: *input.packageName, CurrentVersion: *input.currentVersion, CurrentPath: *input.currentPath, RootDir: *input.rootDir, Platform: *input.platform, Architecture: *input.architecture, Channel: *input.channel, Entrypoint: *input.entrypoint, HealthTimeout: *input.healthTimeout, KeepReleases: *input.keepReleases, }) if err != nil { fatal("\u521b\u5efa\u66f4\u65b0\u7ba1\u7406\u5668\u5931\u8d25: %v", err) } return manager } func parseVersions(value string) map[string]string { result := make(map[string]string) for _, entry := range splitPackages(value) { parts := strings.SplitN(entry, ":", 2) if len(parts) == 2 && strings.TrimSpace(parts[0]) != "" { result[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) } } return result } func printUsage() { fmt.Println("Scout \u8f7b\u91cf\u5316\u5305\u7ba1\u7406\u5ba2\u6237\u7aef") fmt.Println("\u7528\u6cd5\uff1ascout-client [\u53c2\u6570]") fmt.Println("check\u3001download\u3001watch \u7528\u4e8e\u66f4\u65b0\u67e5\u8be2\uff0cupload \u7528\u4e8e\u5f00\u53d1\u8005\u4e0a\u4f20\u3002") fmt.Println("prepare \u53ea\u51c6\u5907\u65b0\u7248\u672c\uff0cactivate \u7531\u7528\u6237\u51b3\u5b9a\u91cd\u542f\u65f6\u673a\uff0chealthy \u786e\u8ba4\u6210\u529f\u540e\u624d\u6e05\u7406\u65e7\u7248\u672c\u3002") }