Help us shape the future of Go! Take the 2026 Go Developer Survey

pv

package
v1.3.104 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package pv cmd/skywire-cli/commands/pv/pv.go c4-vis-cli

Index

Constants

This section is empty.

Variables

View Source
var RootCmd = &cobra.Command{
	Use:   "pv",
	Short: "Public Visors",
	Long: fmt.Sprintf(`List public visors from service discovery
%v/api/services?type=%v

Returns only public keys, one per line.
Use -t to show transport counts per visor.

Cache files are stored in directories named after service hosts.
Set cache dir to "" to disable caching for that service.`, getDeployment().ServiceDiscovery, serviceType),
	Run: func(cmd *cobra.Command, _ []string) {

		if showByType {
			showTransports = true
		}

		if showVersion {
			showTransports = true
		}

		verByPK := map[string]string{}
		sdFullURL := sdURL + "/api/services?type=" + serviceType

		utFullURL := clirpc.IntegratedUptimeURLDays(tpdURL, 1)
		tpdFullURL := tpdURL + "/all-transports"

		sds := clirpc.FetchCachedServiceURL(cmd.Flags(), cacheFile(cacheDirSD, sdFullURL), sdFullURL, cacheFilesAge)
		if rawData {
			internal.PrintRawJSON(cmd.Flags(), sds, string(pretty.Color(pretty.Pretty([]byte(sds)), nil)))
			return
		}

		// Get list of PKs first (we'll need this for transport counting)
		var pks []string

		if noFilterOnline {
			sdJQ := `.[]`
			if country != "" {
				sdJQ += ` | select(.geo.country == "` + country + `")`
			}
			if version != "" {
				sdJQ += ` | select(.version | startswith("` + version + `"))`
			}
			sdJQ += ` | .address | split(":")[0]`

			if !showTransports {
				if isStats {
					count, err := script.Echo(sds).JQ(sdJQ).Replace(`"`, "").CountLines()
					if err != nil {
						internal.PrintFatalError(cmd.Flags(), fmt.Errorf("error: %w", err))
					}
					internal.PrintOutput(cmd.Flags(), struct {
						Count int `json:"count"`
					}{Count: count}, fmt.Sprintf("%v\n", count))
					return
				}
				internal.PrintPipe(cmd.Flags(), script.Echo(sds).JQ(sdJQ).Replace(`"`, ""))
				return
			}

			pks, _ = script.Echo(sds).JQ(sdJQ).Replace(`"`, "").Slice()
		} else {

			uts := clirpc.FetchCachedServiceURL(cmd.Flags(), cacheFile(cacheDirTPD, utFullURL), utFullURL, cacheFilesAge)
			if uts == "" {
				uts = "[]"
			}
			joinedJSON := fmt.Sprintf(`{"sd": %s, "ut": %s}`, sds, uts)

			countryCond := ""
			if country != "" {
				countryCond = ` | select(.geo.country == "` + country + `")`
			}
			versionCond := ""
			if version != "" {
				versionCond = ` | select(.version | startswith("` + version + `"))`
			}

			jqFilter := `
			[ .ut[] | select(.on) | .pk ] as $online
			| .sd[]
			| select((.address | split(":")[0]) as $pk | $pk | IN($online[]))` + countryCond + versionCond + `
			| .address | split(":")[0]
			`

			if !showTransports {
				if isStats {
					count, err := script.Echo(joinedJSON).JQ(jqFilter).Replace(`"`, "").CountLines()
					if err != nil {
						internal.PrintFatalError(cmd.Flags(), fmt.Errorf("error: %w", err))
					}
					internal.PrintOutput(cmd.Flags(), struct {
						Count int `json:"count"`
					}{Count: count}, fmt.Sprintf("%v\n", count))
					return
				}
				internal.PrintPipe(cmd.Flags(), script.Echo(joinedJSON).JQ(jqFilter).Replace(`"`, ""))
				return
			}

			pks, _ = script.Echo(joinedJSON).JQ(jqFilter).Replace(`"`, "").Slice()

			if showVersion {

				verLines, _ := script.Echo(joinedJSON).JQ(`.ut[] | "\(.pk) \(.version // "")"`).Replace(`"`, "").Slice()
				for _, l := range verLines {
					if f := strings.Fields(l); len(f) == 2 {
						verByPK[f[0]] = f[1]
					}
				}
			}
		}

		tpd := clirpc.FetchCachedServiceURL(cmd.Flags(), cacheFile(cacheDirTPD, tpdFullURL), tpdFullURL, cacheFilesAge)

		var entries []*transport.Entry
		if err := json.Unmarshal([]byte(tpd), &entries); err != nil {
			internal.PrintFatalError(cmd.Flags(), fmt.Errorf("failed to parse transport discovery data: %w", err))
		}

		tpCount := make(map[string]int)
		tpByType := make(map[string]map[types.Type]int)
		for _, entry := range entries {

			if entry.Edges[0] == entry.Edges[1] {
				continue
			}
			for _, edge := range entry.Edges {
				pk := edge.String()
				tpCount[pk]++
				if showByType {
					if tpByType[pk] == nil {
						tpByType[pk] = make(map[types.Type]int)
					}
					tpByType[pk][entry.Type]++
				}
			}
		}

		// Build result with transport counts
		type visorInfo struct {
			pk    string
			count int
		}
		var results []visorInfo
		for _, pk := range pks {
			count := tpCount[pk]
			if count >= minTransports {
				results = append(results, visorInfo{pk: pk, count: count})
			}
		}

		sort.Slice(results, func(i, j int) bool {
			return results[i].count > results[j].count
		})

		if isStats {
			internal.PrintOutput(cmd.Flags(), struct {
				Count int `json:"count"`
			}{Count: len(results)}, fmt.Sprintf("%d\n", len(results)))
			return
		}

		if showByType {

			order := types.PreferenceOrder()
			for _, r := range results {
				line := fmt.Sprintf("%s total=%d", r.pk, r.count)
				if showVersion {
					line += " version=" + versionOrDash(verByPK[r.pk])
				}
				for _, t := range order {
					line += fmt.Sprintf(" %s=%d", t, tpByType[r.pk][t])
				}
				cmdutil.Println(line)
			}
			return
		}

		for _, r := range results {
			if showVersion {
				cmdutil.Printf("%s %d %s\n", r.pk, r.count, versionOrDash(verByPK[r.pk]))
				continue
			}
			cmdutil.Printf("%s %d\n", r.pk, r.count)
		}
	},
}

RootCmd is the command for listing public visors

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL