fix: import path should be github.com/ooni/probe-cli/v3 (#200)
See https://github.com/ooni/probe/issues/1335#issuecomment-771499511
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/version"
|
||||
)
|
||||
|
||||
// Run the app. This is the main app entry point
|
||||
func Run() {
|
||||
root.Cmd.Version(version.Version)
|
||||
_, err := root.Cmd.Parse(os.Args[1:])
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failure in main command")
|
||||
os.Exit(2)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package autorun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/autorun"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/onboard"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
)
|
||||
|
||||
var errNotImplemented = errors.New("autorun: not implemented on this platform")
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("autorun", "Run automatic tests in the background")
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
probe, err := root.Init()
|
||||
if err != nil {
|
||||
log.Errorf("%s", err)
|
||||
return err
|
||||
}
|
||||
if err := onboard.MaybeOnboarding(probe); err != nil {
|
||||
log.WithError(err).Error("failed to perform onboarding")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
start := cmd.Command("start", "Start running automatic tests in the background")
|
||||
start.Action(func(_ *kingpin.ParseContext) error {
|
||||
svc := autorun.Get(runtime.GOOS)
|
||||
if svc == nil {
|
||||
return errNotImplemented
|
||||
}
|
||||
if err := svc.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("hint: use 'ooniprobe autorun log stream' to follow logs")
|
||||
return nil
|
||||
})
|
||||
|
||||
stop := cmd.Command("stop", "Stop running automatic tests in the background")
|
||||
stop.Action(func(_ *kingpin.ParseContext) error {
|
||||
svc := autorun.Get(runtime.GOOS)
|
||||
if svc == nil {
|
||||
return errNotImplemented
|
||||
}
|
||||
return svc.Stop()
|
||||
})
|
||||
|
||||
logCmd := cmd.Command("log", "Access background runs logs")
|
||||
stream := logCmd.Command("stream", "Stream background runs logs")
|
||||
stream.Action(func(_ *kingpin.ParseContext) error {
|
||||
svc := autorun.Get(runtime.GOOS)
|
||||
if svc == nil {
|
||||
return errNotImplemented
|
||||
}
|
||||
return svc.LogStream()
|
||||
})
|
||||
|
||||
show := logCmd.Command("show", "Show background runs logs")
|
||||
show.Action(func(_ *kingpin.ParseContext) error {
|
||||
svc := autorun.Get(runtime.GOOS)
|
||||
if svc == nil {
|
||||
return errNotImplemented
|
||||
}
|
||||
return svc.LogShow()
|
||||
})
|
||||
|
||||
status := cmd.Command("status", "Shows autorun instance status")
|
||||
status.Action(func(_ *kingpin.ParseContext) error {
|
||||
svc := autorun.Get(runtime.GOOS)
|
||||
if svc == nil {
|
||||
return errNotImplemented
|
||||
}
|
||||
out, err := svc.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("status: %s", out)
|
||||
switch out {
|
||||
case autorun.StatusRunning:
|
||||
log.Info("hint: use 'ooniprobe autorun stop' to stop")
|
||||
log.Info("hint: use 'ooniprobe autorun log stream' to follow logs")
|
||||
case autorun.StatusScheduled:
|
||||
log.Info("hint: use 'ooniprobe autorun stop' to stop")
|
||||
log.Info("hint: use 'ooniprobe autorun log show' to see previous logs")
|
||||
case autorun.StatusStopped:
|
||||
log.Info("hint: use 'ooniprobe autorun start' to start")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/ooni"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/output"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("geoip", "Perform a geoip lookup")
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
return dogeoip(defaultconfig)
|
||||
})
|
||||
}
|
||||
|
||||
type dogeoipconfig struct {
|
||||
Logger log.Interface
|
||||
NewProbeCLI func() (ooni.ProbeCLI, error)
|
||||
SectionTitle func(string)
|
||||
}
|
||||
|
||||
var defaultconfig = dogeoipconfig{
|
||||
Logger: log.Log,
|
||||
NewProbeCLI: root.NewProbeCLI,
|
||||
SectionTitle: output.SectionTitle,
|
||||
}
|
||||
|
||||
func dogeoip(config dogeoipconfig) error {
|
||||
config.SectionTitle("GeoIP lookup")
|
||||
probeCLI, err := config.NewProbeCLI()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
engine, err := probeCLI.NewProbeEngine()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer engine.Close()
|
||||
|
||||
err = engine.MaybeLookupLocation()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.Logger.WithFields(log.Fields{
|
||||
"type": "table",
|
||||
"asn": engine.ProbeASNString(),
|
||||
"network_name": engine.ProbeNetworkName(),
|
||||
"country_code": engine.ProbeCC(),
|
||||
"ip": engine.ProbeIP(),
|
||||
}).Info("Looked up your location")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package geoip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/ooni"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/oonitest"
|
||||
)
|
||||
|
||||
func TestNewProbeCLIFailed(t *testing.T) {
|
||||
fo := &oonitest.FakeOutput{}
|
||||
expected := errors.New("mocked error")
|
||||
err := dogeoip(dogeoipconfig{
|
||||
SectionTitle: fo.SectionTitle,
|
||||
NewProbeCLI: func() (ooni.ProbeCLI, error) {
|
||||
return nil, expected
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if len(fo.FakeSectionTitle) != 1 {
|
||||
t.Fatal("invalid section title list size")
|
||||
}
|
||||
if fo.FakeSectionTitle[0] != "GeoIP lookup" {
|
||||
t.Fatal("unexpected string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewProbeEngineFailed(t *testing.T) {
|
||||
fo := &oonitest.FakeOutput{}
|
||||
expected := errors.New("mocked error")
|
||||
cli := &oonitest.FakeProbeCLI{
|
||||
FakeProbeEngineErr: expected,
|
||||
}
|
||||
err := dogeoip(dogeoipconfig{
|
||||
SectionTitle: fo.SectionTitle,
|
||||
NewProbeCLI: func() (ooni.ProbeCLI, error) {
|
||||
return cli, nil
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if len(fo.FakeSectionTitle) != 1 {
|
||||
t.Fatal("invalid section title list size")
|
||||
}
|
||||
if fo.FakeSectionTitle[0] != "GeoIP lookup" {
|
||||
t.Fatal("unexpected string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaybeLookupLocationFailed(t *testing.T) {
|
||||
fo := &oonitest.FakeOutput{}
|
||||
expected := errors.New("mocked error")
|
||||
engine := &oonitest.FakeProbeEngine{
|
||||
FakeMaybeLookupLocation: expected,
|
||||
}
|
||||
cli := &oonitest.FakeProbeCLI{
|
||||
FakeProbeEnginePtr: engine,
|
||||
}
|
||||
err := dogeoip(dogeoipconfig{
|
||||
SectionTitle: fo.SectionTitle,
|
||||
NewProbeCLI: func() (ooni.ProbeCLI, error) {
|
||||
return cli, nil
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if len(fo.FakeSectionTitle) != 1 {
|
||||
t.Fatal("invalid section title list size")
|
||||
}
|
||||
if fo.FakeSectionTitle[0] != "GeoIP lookup" {
|
||||
t.Fatal("unexpected string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaybeLookupLocationSuccess(t *testing.T) {
|
||||
fo := &oonitest.FakeOutput{}
|
||||
engine := &oonitest.FakeProbeEngine{
|
||||
FakeProbeASNString: "AS30722",
|
||||
FakeProbeCC: "IT",
|
||||
FakeProbeNetworkName: "Vodafone Italia S.p.A.",
|
||||
FakeProbeIP: "130.25.90.216",
|
||||
}
|
||||
cli := &oonitest.FakeProbeCLI{
|
||||
FakeProbeEnginePtr: engine,
|
||||
}
|
||||
handler := &oonitest.FakeLoggerHandler{}
|
||||
err := dogeoip(dogeoipconfig{
|
||||
SectionTitle: fo.SectionTitle,
|
||||
NewProbeCLI: func() (ooni.ProbeCLI, error) {
|
||||
return cli, nil
|
||||
},
|
||||
Logger: &log.Logger{
|
||||
Handler: handler,
|
||||
Level: log.DebugLevel,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(fo.FakeSectionTitle) != 1 {
|
||||
t.Fatal("invalid section title list size")
|
||||
}
|
||||
if fo.FakeSectionTitle[0] != "GeoIP lookup" {
|
||||
t.Fatal("unexpected string")
|
||||
}
|
||||
if len(handler.FakeEntries) != 1 {
|
||||
t.Fatal("invalid number of written entries")
|
||||
}
|
||||
entry := handler.FakeEntries[0]
|
||||
if entry.Level != log.InfoLevel {
|
||||
t.Fatal("invalid log level")
|
||||
}
|
||||
if entry.Message != "Looked up your location" {
|
||||
t.Fatal("invalid .Message")
|
||||
}
|
||||
if entry.Fields["asn"].(string) != "AS30722" {
|
||||
t.Fatal("invalid asn")
|
||||
}
|
||||
if entry.Fields["country_code"].(string) != "IT" {
|
||||
t.Fatal("invalid asn")
|
||||
}
|
||||
if entry.Fields["network_name"].(string) != "Vodafone Italia S.p.A." {
|
||||
t.Fatal("invalid asn")
|
||||
}
|
||||
if entry.Fields["ip"].(string) != "130.25.90.216" {
|
||||
t.Fatal("invalid asn")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/ooni"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("info", "Display information about OONI Probe")
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
return doinfo(defaultconfig)
|
||||
})
|
||||
}
|
||||
|
||||
type doinfoconfig struct {
|
||||
Logger log.Interface
|
||||
NewProbeCLI func() (ooni.ProbeCLI, error)
|
||||
}
|
||||
|
||||
var defaultconfig = doinfoconfig{
|
||||
Logger: log.Log,
|
||||
NewProbeCLI: root.NewProbeCLI,
|
||||
}
|
||||
|
||||
func doinfo(config doinfoconfig) error {
|
||||
probeCLI, err := config.NewProbeCLI()
|
||||
if err != nil {
|
||||
config.Logger.Errorf("%s", err)
|
||||
return err
|
||||
}
|
||||
config.Logger.WithFields(log.Fields{"path": probeCLI.Home()}).Info("Home")
|
||||
config.Logger.WithFields(log.Fields{"path": probeCLI.TempDir()}).Info("TempDir")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/ooni"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/oonitest"
|
||||
)
|
||||
|
||||
func TestNewProbeCLIFailed(t *testing.T) {
|
||||
expected := errors.New("mocked error")
|
||||
handler := &oonitest.FakeLoggerHandler{}
|
||||
err := doinfo(doinfoconfig{
|
||||
NewProbeCLI: func() (ooni.ProbeCLI, error) {
|
||||
return nil, expected
|
||||
},
|
||||
Logger: &log.Logger{
|
||||
Handler: handler,
|
||||
Level: log.DebugLevel,
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, expected) {
|
||||
t.Fatalf("not the error we expected: %+v", err)
|
||||
}
|
||||
if len(handler.FakeEntries) != 1 {
|
||||
t.Fatal("invalid number of log entries")
|
||||
}
|
||||
entry := handler.FakeEntries[0]
|
||||
if entry.Level != log.ErrorLevel {
|
||||
t.Fatal("invalid log level")
|
||||
}
|
||||
if entry.Message != "mocked error" {
|
||||
t.Fatal("invalid .Message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuccess(t *testing.T) {
|
||||
handler := &oonitest.FakeLoggerHandler{}
|
||||
cli := &oonitest.FakeProbeCLI{
|
||||
FakeHome: "fakehome",
|
||||
FakeTempDir: "faketempdir",
|
||||
}
|
||||
err := doinfo(doinfoconfig{
|
||||
NewProbeCLI: func() (ooni.ProbeCLI, error) {
|
||||
return cli, nil
|
||||
},
|
||||
Logger: &log.Logger{
|
||||
Handler: handler,
|
||||
Level: log.DebugLevel,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(handler.FakeEntries) != 2 {
|
||||
t.Fatal("invalid number of log entries")
|
||||
}
|
||||
entry := handler.FakeEntries[0]
|
||||
if entry.Level != log.InfoLevel {
|
||||
t.Fatal("invalid log level")
|
||||
}
|
||||
if entry.Message != "Home" {
|
||||
t.Fatal("invalid .Message")
|
||||
}
|
||||
if entry.Fields["path"].(string) != "fakehome" {
|
||||
t.Fatal("invalid path")
|
||||
}
|
||||
entry = handler.FakeEntries[1]
|
||||
if entry.Level != log.InfoLevel {
|
||||
t.Fatal("invalid log level")
|
||||
}
|
||||
if entry.Message != "TempDir" {
|
||||
t.Fatal("invalid .Message")
|
||||
}
|
||||
if entry.Fields["path"].(string) != "faketempdir" {
|
||||
t.Fatal("invalid path")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/database"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/output"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("list", "List results")
|
||||
resultID := cmd.Arg("id", "the id of the result to list measurements for").Int64()
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
probeCLI, err := root.Init()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to initialize root context")
|
||||
return err
|
||||
}
|
||||
if *resultID > 0 {
|
||||
measurements, err := database.ListMeasurements(probeCLI.DB(), *resultID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to list measurements")
|
||||
return err
|
||||
}
|
||||
msmtSummary := output.MeasurementSummaryData{
|
||||
TotalCount: 0,
|
||||
AnomalyCount: 0,
|
||||
DataUsageUp: 0.0,
|
||||
DataUsageDown: 0.0,
|
||||
TotalRuntime: 0,
|
||||
ASN: 0,
|
||||
NetworkName: "",
|
||||
NetworkCountryCode: "ZZ",
|
||||
}
|
||||
isFirst := true
|
||||
isLast := false
|
||||
for idx, msmt := range measurements {
|
||||
if idx > 0 {
|
||||
isFirst = false
|
||||
}
|
||||
if idx == len(measurements)-1 {
|
||||
isLast = true
|
||||
}
|
||||
// We assume that since these are summary level information the first
|
||||
// item will contain the information necessary.
|
||||
if isFirst {
|
||||
msmtSummary.TotalRuntime = msmt.Result.Runtime
|
||||
msmtSummary.DataUsageUp = msmt.DataUsageUp
|
||||
msmtSummary.DataUsageDown = msmt.DataUsageDown
|
||||
msmtSummary.NetworkName = msmt.NetworkName
|
||||
msmtSummary.NetworkCountryCode = msmt.Network.CountryCode
|
||||
msmtSummary.ASN = msmt.ASN
|
||||
msmtSummary.StartTime = msmt.Measurement.StartTime
|
||||
}
|
||||
if msmt.IsAnomaly.Bool == true {
|
||||
msmtSummary.AnomalyCount++
|
||||
}
|
||||
msmtSummary.TotalCount++
|
||||
output.MeasurementItem(msmt, isFirst, isLast)
|
||||
}
|
||||
output.MeasurementSummary(msmtSummary)
|
||||
} else {
|
||||
doneResults, incompleteResults, err := database.ListResults(probeCLI.DB())
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to list results")
|
||||
return err
|
||||
}
|
||||
if len(incompleteResults) > 0 {
|
||||
output.SectionTitle("Incomplete results")
|
||||
}
|
||||
for idx, result := range incompleteResults {
|
||||
output.ResultItem(output.ResultItemData{
|
||||
ID: result.Result.ID,
|
||||
Index: idx,
|
||||
TotalCount: len(incompleteResults),
|
||||
Name: result.TestGroupName,
|
||||
StartTime: result.StartTime,
|
||||
NetworkName: result.Network.NetworkName,
|
||||
Country: result.Network.CountryCode,
|
||||
ASN: result.Network.ASN,
|
||||
MeasurementCount: 0,
|
||||
MeasurementAnomalyCount: 0,
|
||||
TestKeys: "{}", // FIXME this used to be Summary we probably need to use a list now
|
||||
Done: result.IsDone,
|
||||
DataUsageUp: result.DataUsageUp,
|
||||
DataUsageDown: result.DataUsageDown,
|
||||
})
|
||||
}
|
||||
resultSummary := output.ResultSummaryData{}
|
||||
netCount := make(map[uint]int)
|
||||
output.SectionTitle("Results")
|
||||
for idx, result := range doneResults {
|
||||
totalCount, anmlyCount, err := database.GetMeasurementCounts(probeCLI.DB(), result.Result.ID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to list measurement counts")
|
||||
}
|
||||
testKeys, err := database.GetResultTestKeys(probeCLI.DB(), result.Result.ID)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to get testKeys")
|
||||
}
|
||||
output.ResultItem(output.ResultItemData{
|
||||
ID: result.Result.ID,
|
||||
Index: idx,
|
||||
TotalCount: len(doneResults),
|
||||
Name: result.TestGroupName,
|
||||
StartTime: result.StartTime,
|
||||
NetworkName: result.Network.NetworkName,
|
||||
Country: result.Network.CountryCode,
|
||||
ASN: result.Network.ASN,
|
||||
TestKeys: testKeys,
|
||||
MeasurementCount: totalCount,
|
||||
MeasurementAnomalyCount: anmlyCount,
|
||||
Done: result.IsDone,
|
||||
DataUsageUp: result.DataUsageUp,
|
||||
DataUsageDown: result.DataUsageDown,
|
||||
})
|
||||
resultSummary.TotalTests++
|
||||
netCount[result.Network.ASN]++
|
||||
resultSummary.TotalDataUsageUp += result.DataUsageUp
|
||||
resultSummary.TotalDataUsageDown += result.DataUsageDown
|
||||
}
|
||||
resultSummary.TotalNetworks = int64(len(netCount))
|
||||
output.ResultSummary(resultSummary)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package onboard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/fatih/color"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/config"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/ooni"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/output"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
)
|
||||
|
||||
// Onboarding start the interactive onboarding procedure
|
||||
func Onboarding(config *config.Config) error {
|
||||
output.SectionTitle("What is OONI Probe?")
|
||||
|
||||
fmt.Println()
|
||||
output.Paragraph("Your tool for detecting internet censorship!")
|
||||
fmt.Println()
|
||||
output.Paragraph("OONI Probe checks whether your provider blocks access to sites and services. Run OONI Probe to collect evidence of internet censorship and to measure your network performance.")
|
||||
fmt.Println()
|
||||
err := output.PressEnterToContinue("Press 'Enter' to continue...")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output.SectionTitle("Heads Up")
|
||||
fmt.Println()
|
||||
output.Bullet("Anyone monitoring your internet activity (such as your government or ISP) may be able to see that you are running OONI Probe.")
|
||||
fmt.Println()
|
||||
output.Bullet("The network data you will collect will automatically be published (unless you opt-out in the settings).")
|
||||
fmt.Println()
|
||||
output.Bullet("You may test objectionable sites.")
|
||||
fmt.Println()
|
||||
output.Bullet("Read the documentation to learn more.")
|
||||
fmt.Println()
|
||||
err = output.PressEnterToContinue("Press 'Enter' to continue...")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output.SectionTitle("Pop Quiz!")
|
||||
output.Paragraph("")
|
||||
answer := ""
|
||||
quiz1 := &survey.Select{
|
||||
Message: "Anyone monitoring my internet activity may be able to see that I am running OONI Probe.",
|
||||
Options: []string{"true", "false"},
|
||||
Default: "true",
|
||||
}
|
||||
if err := survey.AskOne(quiz1, &answer, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if answer != "true" {
|
||||
output.Paragraph(color.RedString("Actually..."))
|
||||
output.Paragraph("OONI Probe is not a privacy tool. Therefore, anyone monitoring your internet activity may be able to see which software you are running.")
|
||||
} else {
|
||||
output.Paragraph(color.BlueString("Good job!"))
|
||||
}
|
||||
answer = ""
|
||||
quiz2 := &survey.Select{
|
||||
Message: "The network data I will collect will automatically be published (unless I opt-out in the settings).",
|
||||
Options: []string{"true", "false"},
|
||||
Default: "true",
|
||||
}
|
||||
if err := survey.AskOne(quiz2, &answer, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if answer != "true" {
|
||||
output.Paragraph(color.RedString("Actually..."))
|
||||
output.Paragraph("The network data you will collect will automatically be published to increase transparency of internet censorship (unless you opt-out in the settings).")
|
||||
} else {
|
||||
output.Paragraph(color.BlueString("Well done!"))
|
||||
}
|
||||
|
||||
changeDefaults := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: "Do you want to change the default settings?",
|
||||
Default: false,
|
||||
}
|
||||
if err := survey.AskOne(prompt, &changeDefaults, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings := struct {
|
||||
IncludeIP bool
|
||||
IncludeNetwork bool
|
||||
UploadResults bool
|
||||
SendCrashReports bool
|
||||
}{}
|
||||
settings.IncludeIP = false
|
||||
settings.IncludeNetwork = true
|
||||
settings.UploadResults = true
|
||||
settings.SendCrashReports = true
|
||||
|
||||
if changeDefaults == true {
|
||||
var qs = []*survey.Question{
|
||||
{
|
||||
Name: "IncludeIP",
|
||||
Prompt: &survey.Confirm{Message: "Should we include your IP?"},
|
||||
},
|
||||
{
|
||||
Name: "IncludeNetwork",
|
||||
Prompt: &survey.Confirm{
|
||||
Message: "Can we include your network name?",
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "UploadResults",
|
||||
Prompt: &survey.Confirm{
|
||||
Message: "Can we upload your results?",
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "SendCrashReports",
|
||||
Prompt: &survey.Confirm{
|
||||
Message: "Can we send crash reports to OONI?",
|
||||
Default: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := survey.Ask(qs, &settings); err != nil {
|
||||
log.WithError(err).Error("there was an error in parsing your responses")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
config.Lock()
|
||||
config.InformedConsent = true
|
||||
config.Advanced.SendCrashReports = settings.SendCrashReports
|
||||
config.Sharing.UploadResults = settings.UploadResults
|
||||
config.Unlock()
|
||||
|
||||
if err := config.Write(); err != nil {
|
||||
log.WithError(err).Error("failed to write config file")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaybeOnboarding will run the onboarding process only if the informed consent
|
||||
// config option is set to false
|
||||
func MaybeOnboarding(probe *ooni.Probe) error {
|
||||
if probe.Config().InformedConsent == false {
|
||||
if probe.IsBatch() == true {
|
||||
return errors.New("cannot run onboarding in batch mode")
|
||||
}
|
||||
if err := Onboarding(probe.Config()); err != nil {
|
||||
return errors.Wrap(err, "onboarding")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("onboard", "Starts the onboarding process")
|
||||
|
||||
yes := cmd.Flag("yes", "Answer yes to all the onboarding questions.").Bool()
|
||||
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
probe, err := root.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *yes == true {
|
||||
probe.Config().Lock()
|
||||
probe.Config().InformedConsent = true
|
||||
probe.Config().Unlock()
|
||||
|
||||
if err := probe.Config().Write(); err != nil {
|
||||
log.WithError(err).Error("failed to write config file")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if probe.IsBatch() == true {
|
||||
return errors.New("cannot do onboarding in batch mode")
|
||||
}
|
||||
|
||||
return Onboarding(probe.Config())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package reset
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("reset", "Cleanup an old or experimental installation")
|
||||
force := cmd.Flag("force", "Force deleting the OONI Home").Bool()
|
||||
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
ctx, err := root.Init()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to init root context")
|
||||
return err
|
||||
}
|
||||
// We need to first the DB otherwise the DB will be rewritten on close when
|
||||
// we delete the home directory.
|
||||
err = ctx.DB().Close()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to close the DB")
|
||||
return err
|
||||
}
|
||||
if *force == true {
|
||||
os.RemoveAll(ctx.Home())
|
||||
log.Infof("Deleted %s", ctx.Home())
|
||||
} else {
|
||||
log.Infof("Run with --force to delete %s", ctx.Home())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package rm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/database"
|
||||
survey "gopkg.in/AlecAivazis/survey.v1"
|
||||
db "upper.io/db.v3"
|
||||
"upper.io/db.v3/lib/sqlbuilder"
|
||||
)
|
||||
|
||||
func deleteAll(sess sqlbuilder.Database, skipInteractive bool) error {
|
||||
if skipInteractive == false {
|
||||
answer := ""
|
||||
confirm := &survey.Select{
|
||||
Message: fmt.Sprintf("Are you sure you wish to delete ALL results"),
|
||||
Options: []string{"true", "false"},
|
||||
Default: "false",
|
||||
}
|
||||
survey.AskOne(confirm, &answer, nil)
|
||||
if answer == "false" {
|
||||
return errors.New("canceled by user")
|
||||
}
|
||||
}
|
||||
doneResults, incompleteResults, err := database.ListResults(sess)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to list results")
|
||||
return err
|
||||
}
|
||||
cnt := 0
|
||||
for _, result := range incompleteResults {
|
||||
err = database.DeleteResult(sess, result.Result.ID)
|
||||
if err == db.ErrNoMoreRows {
|
||||
log.WithError(err).Errorf("failed to delete result #%d", result.Result.ID)
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
for _, result := range doneResults {
|
||||
err = database.DeleteResult(sess, result.Result.ID)
|
||||
if err == db.ErrNoMoreRows {
|
||||
log.WithError(err).Errorf("failed to delete result #%d", result.Result.ID)
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
log.Infof("Deleted #%d measurements", cnt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("rm", "Delete a result")
|
||||
yes := cmd.Flag("yes", "Skip interactive prompt").Bool()
|
||||
all := cmd.Flag("all", "Delete all measurements").Bool()
|
||||
|
||||
resultID := cmd.Arg("id", "the id of the result to delete").Int64()
|
||||
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
ctx, err := root.Init()
|
||||
if err != nil {
|
||||
log.Errorf("%s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if *all == true {
|
||||
return deleteAll(ctx.DB(), *yes)
|
||||
}
|
||||
|
||||
if *yes == true {
|
||||
err = database.DeleteResult(ctx.DB(), *resultID)
|
||||
if err == db.ErrNoMoreRows {
|
||||
return errors.New("result not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
answer := ""
|
||||
confirm := &survey.Select{
|
||||
Message: fmt.Sprintf("Are you sure you wish to delete the result #%d", *resultID),
|
||||
Options: []string{"true", "false"},
|
||||
Default: "false",
|
||||
}
|
||||
survey.AskOne(confirm, &answer, nil)
|
||||
if answer == "false" {
|
||||
return errors.New("canceled by user")
|
||||
}
|
||||
err = database.DeleteResult(ctx.DB(), *resultID)
|
||||
if err == db.ErrNoMoreRows {
|
||||
return errors.New("result not found")
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package root
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/log/handlers/batch"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/log/handlers/cli"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/log/handlers/syslog"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/ooni"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/utils"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/version"
|
||||
)
|
||||
|
||||
// Cmd is the root command
|
||||
var Cmd = kingpin.New("ooniprobe", "")
|
||||
|
||||
// Command is syntax sugar for defining sub-commands
|
||||
var Command = Cmd.Command
|
||||
|
||||
// Init should be called by all subcommand that care to have a ooni.Context instance
|
||||
var Init func() (*ooni.Probe, error)
|
||||
|
||||
// NewProbeCLI is like Init but returns a ooni.ProbeCLI instead.
|
||||
func NewProbeCLI() (ooni.ProbeCLI, error) {
|
||||
probeCLI, err := Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return probeCLI, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
configPath := Cmd.Flag("config", "Set a custom config file path").Short('c').String()
|
||||
|
||||
isVerbose := Cmd.Flag("verbose", "Enable verbose log output.").Short('v').Bool()
|
||||
isBatch := Cmd.Flag("batch", "Enable batch command line usage.").Bool()
|
||||
logHandler := Cmd.Flag(
|
||||
"log-handler", "Set the desired log handler (one of: batch, cli, syslog)",
|
||||
).String()
|
||||
|
||||
softwareName := Cmd.Flag(
|
||||
"software-name", "Override application name",
|
||||
).Default("ooniprobe-cli").String()
|
||||
softwareVersion := Cmd.Flag(
|
||||
"software-version", "Override the application version",
|
||||
).Default(version.Version).String()
|
||||
|
||||
Cmd.PreAction(func(ctx *kingpin.ParseContext) error {
|
||||
// TODO(bassosimone): we need to properly deprecate --batch
|
||||
// in favour of more granular command line flags.
|
||||
if *isBatch && *logHandler != "" {
|
||||
log.Fatal("cannot specify --batch and --log-handler together")
|
||||
}
|
||||
if *isBatch {
|
||||
*logHandler = "batch"
|
||||
}
|
||||
switch *logHandler {
|
||||
case "batch":
|
||||
log.SetHandler(batch.Default)
|
||||
case "cli", "":
|
||||
log.SetHandler(cli.Default)
|
||||
case "syslog":
|
||||
log.SetHandler(syslog.Default)
|
||||
default:
|
||||
log.Fatalf("unknown --log-handler: %s", *logHandler)
|
||||
}
|
||||
if *isVerbose {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.Debugf("ooni version %s", version.Version)
|
||||
}
|
||||
|
||||
Init = func() (*ooni.Probe, error) {
|
||||
var err error
|
||||
|
||||
homePath, err := utils.GetOONIHome()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
probe := ooni.NewProbe(*configPath, homePath)
|
||||
err = probe.Init(*softwareName, *softwareVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if *isBatch {
|
||||
probe.SetIsBatch(true)
|
||||
}
|
||||
|
||||
return probe, nil
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package run
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/fatih/color"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/onboard"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/nettests"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/ooni"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("run", "Run a test group or OONI Run link")
|
||||
noCollector := cmd.Flag("no-collector", "Disable uploading measurements to a collector").Bool()
|
||||
|
||||
var probe *ooni.Probe
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
var err error
|
||||
probe, err = root.Init()
|
||||
if err != nil {
|
||||
log.Errorf("%s", err)
|
||||
return err
|
||||
}
|
||||
if err = onboard.MaybeOnboarding(probe); err != nil {
|
||||
log.WithError(err).Error("failed to perform onboarding")
|
||||
return err
|
||||
}
|
||||
if *noCollector == true {
|
||||
probe.Config().Sharing.UploadResults = false
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
functionalRun := func(pred func(name string, gr nettests.Group) bool) error {
|
||||
for name, group := range nettests.All {
|
||||
if pred(name, group) != true {
|
||||
continue
|
||||
}
|
||||
log.Infof("Running %s tests", color.BlueString(name))
|
||||
conf := nettests.RunGroupConfig{GroupName: name, Probe: probe}
|
||||
if err := nettests.RunGroup(conf); err != nil {
|
||||
log.WithError(err).Errorf("failed to run %s", name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
genRunWithGroupName := func(targetName string) func(*kingpin.ParseContext) error {
|
||||
return func(*kingpin.ParseContext) error {
|
||||
return functionalRun(func(groupName string, gr nettests.Group) bool {
|
||||
return groupName == targetName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
websitesCmd := cmd.Command("websites", "")
|
||||
inputFile := websitesCmd.Flag("input-file", "File containing input URLs").Strings()
|
||||
input := websitesCmd.Flag("input", "Test the specified URL").Strings()
|
||||
websitesCmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
log.Infof("Running %s tests", color.BlueString("websites"))
|
||||
return nettests.RunGroup(nettests.RunGroupConfig{
|
||||
GroupName: "websites",
|
||||
Probe: probe,
|
||||
InputFiles: *inputFile,
|
||||
Inputs: *input,
|
||||
})
|
||||
})
|
||||
|
||||
easyRuns := []string{"im", "performance", "circumvention", "middlebox"}
|
||||
for _, name := range easyRuns {
|
||||
cmd.Command(name, "").Action(genRunWithGroupName(name))
|
||||
}
|
||||
|
||||
unattendedCmd := cmd.Command("unattended", "")
|
||||
unattendedCmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
if runtime.GOOS == "darwin" {
|
||||
// Until we have enabled the check-in API we're called every
|
||||
// hour on darwin and we need to self throttle.
|
||||
// TODO(bassosimone): switch to check-in and remove this hack.
|
||||
const veryFew = 10
|
||||
probe.Config().Nettests.WebsitesURLLimit = veryFew
|
||||
}
|
||||
return functionalRun(func(name string, gr nettests.Group) bool {
|
||||
return gr.UnattendedOK == true
|
||||
})
|
||||
})
|
||||
|
||||
allCmd := cmd.Command("all", "").Default()
|
||||
allCmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
return functionalRun(func(name string, gr nettests.Group) bool {
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package nettest
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/database"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/output"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("show", "Show a specific measurement")
|
||||
msmtID := cmd.Arg("id", "the id of the measurement to show").Required().Int64()
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
ctx, err := root.Init()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to initialize root context")
|
||||
return err
|
||||
}
|
||||
msmt, err := database.GetMeasurementJSON(ctx.DB(), *msmtID)
|
||||
if err != nil {
|
||||
log.Errorf("error: %v", err)
|
||||
return err
|
||||
}
|
||||
output.MeasurementJSON(msmt)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package upload
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/apex/log"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("upload", "Upload a specific measurement")
|
||||
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
log.Info("Uploading")
|
||||
log.Error("this function is not implemented")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/cli/root"
|
||||
"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/version"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := root.Command("version", "Show version.")
|
||||
cmd.Action(func(_ *kingpin.ParseContext) error {
|
||||
fmt.Println(version.Version)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user