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:
Simone Basso
2021-02-02 10:32:46 +01:00
committed by GitHub
parent faa9308b1e
commit b1ce300c8d
68 changed files with 86 additions and 85 deletions
+20
View File
@@ -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
})
}
+57
View File
@@ -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")
}
}
+36
View File
@@ -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")
}
}
+128
View File
@@ -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())
})
}
+37
View File
@@ -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
})
}
+94
View File
@@ -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
})
}
+94
View File
@@ -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
})
}
+97
View File
@@ -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
})
})
}
+28
View File
@@ -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
})
}