diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cde0123 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..f48ea06 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,47 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com + +# The lines below are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/need to use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 2 + +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + - go generate ./... + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + - main: ./cmd/openstatus + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of `uname`. + name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/config.openstatus.yaml b/config.openstatus.yaml new file mode 100644 index 0000000..f2a0360 --- /dev/null +++ b/config.openstatus.yaml @@ -0,0 +1,5 @@ +tests: + ids: + - 2260 + - 2264 + diff --git a/config.openstatus.yml b/config.openstatus.yml deleted file mode 100644 index 8fc733e..0000000 --- a/config.openstatus.yml +++ /dev/null @@ -1,5 +0,0 @@ -tests: - - id: - - 1 - - 660 - diff --git a/go.mod b/go.mod index b12d203..7e0eeee 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,14 @@ require github.com/urfave/cli/v3 v3.0.0-alpha9.2 // direct require ( github.com/fatih/color v1.18.0 + github.com/knadh/koanf/parsers/yaml v0.1.0 + github.com/knadh/koanf/providers/file v1.1.2 github.com/knadh/koanf/v2 v2.1.1 github.com/rodaine/table v1.3.0 ) require ( + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -18,4 +21,5 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect golang.org/x/sys v0.26.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5d41651..142b34b 100644 --- a/go.sum +++ b/go.sum @@ -3,12 +3,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= +github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= +github.com/knadh/koanf/providers/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w= +github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI= github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -43,6 +49,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/config/config.go b/internal/config/config.go index 74f8ded..a0689d0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,6 +1,11 @@ package config -import "github.com/knadh/koanf/v2" +import ( + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/file" + + "github.com/knadh/koanf/v2" +) var k = koanf.New(".") @@ -11,3 +16,17 @@ type TestsConfig struct { type Config struct { Tests TestsConfig `koanf:"tests"` } + +func ReadConfig(path string) (*Config, error) { + k.Load(file.Provider(path), yaml.Parser()) + + var out Config + + err := k.Unmarshal("", &out) + if err != nil { + return nil, err + } + + return &out, nil + +} diff --git a/internal/monitors/monitor_info.go b/internal/monitors/monitor_info.go index aebcf4a..2eb00cc 100644 --- a/internal/monitors/monitor_info.go +++ b/internal/monitors/monitor_info.go @@ -51,7 +51,10 @@ func GetMonitorInfoCmd() *cli.Command { fmt.Println("Monitor information") monitorId := cmd.Args().Get(0) err := getMonitorInfo(http.DefaultClient, cmd.String("access-token"), monitorId) - return err + if err != nil { + return cli.Exit("Failed to get monitor information", 1) + } + return nil }, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/internal/monitors/monitor_trigger.go b/internal/monitors/monitor_trigger.go index 1e15675..7df1890 100644 --- a/internal/monitors/monitor_trigger.go +++ b/internal/monitors/monitor_trigger.go @@ -100,9 +100,8 @@ func GetMonitorsTriggerCmd() *cli.Command { monitorId := cmd.Args().Get(0) err := monitorTrigger(http.DefaultClient, cmd.String("access-token"), monitorId) if err != nil { - return err + return cli.Exit("Failed to trigger monitor", 1) } - // fmt.Println("Triggering monitor test", r) return nil }, } diff --git a/internal/monitors/monitors_list.go b/internal/monitors/monitors_list.go index a2a4244..909e21e 100644 --- a/internal/monitors/monitors_list.go +++ b/internal/monitors/monitors_list.go @@ -72,7 +72,10 @@ func GetMonitorsListCmd() *cli.Command { }, Action: func(ctx context.Context, cmd *cli.Command) error { fmt.Println("List of all monitors") - listMonitors(http.DefaultClient, cmd.String("access-token")) + err := listMonitors(http.DefaultClient, cmd.String("access-token")) + if err != nil { + return cli.Exit("Failed to list monitors", 1) + } return nil }, } diff --git a/internal/run/run.go b/internal/run/run.go index 8a74741..44e051f 100644 --- a/internal/run/run.go +++ b/internal/run/run.go @@ -2,11 +2,77 @@ package run import ( "context" + "encoding/json" "fmt" + "io" + "net/http" + "sync" + "github.com/fatih/color" + "github.com/openstatusHQ/cli/internal/config" + "github.com/openstatusHQ/cli/internal/monitors" + "github.com/rodaine/table" "github.com/urfave/cli/v3" ) +func monitorTrigger(httpClient *http.Client, apiKey string, monitorId string) error { + + if monitorId == "" { + return fmt.Errorf("Monitor ID is required") + } + + url := fmt.Sprintf("https://api.openstatus.dev/v1/monitor/%s/run", monitorId) + + req, err := http.NewRequest("POST", url, nil) + if err != nil { + return err + } + req.Header.Add("x-openstatus-key", apiKey) + res, err := httpClient.Do(req) + if err != nil { + return err + } + + if res.StatusCode != http.StatusOK { + return fmt.Errorf("Failed to trigger monitor test") + } + + defer res.Body.Close() + body, _ := io.ReadAll(res.Body) + + var result []monitors.RunResult + err = json.Unmarshal(body, &result) + if err != nil { + return err + } + fmt.Println("Results for monitor:", monitorId) + headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() + columnFmt := color.New(color.FgYellow).SprintfFunc() + + tbl := table.New("Region", "Latency (ms)", "Status") + tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) + + var inError bool + for _, r := range result { + if r.Error != "" { + inError = true + tbl.AddRow(r.Region, r.Latency, color.RedString("❌")) + } else { + tbl.AddRow(r.Region, r.Latency, color.GreenString("✔")) + } + + } + tbl.Print() + + if inError { + fmt.Println(color.RedString("Some regions failed")) + return fmt.Errorf("Some regions failed") + } else { + fmt.Println(color.GreenString("All regions passed")) + } + return nil +} + func RunCmd() *cli.Command { runCmd := cli.Command{ Name: "run", @@ -14,8 +80,35 @@ func RunCmd() *cli.Command { Usage: "Run your synthetics tests defined in your configuration file", Action: func(ctx context.Context, cmd *cli.Command) error { - fmt.Println(cmd.String("config")) - fmt.Println("Test ran 🔥") + path := cmd.String("config") + conf, err := config.ReadConfig(path) + if err != nil { + return err + } + size := len(conf.Tests.Ids) + ch := make(chan error, size) + + fmt.Println("Tests are running") + + var wg sync.WaitGroup + + for _, id := range conf.Tests.Ids { + wg.Add(1) + go func(id int) { + defer wg.Done() + if err := monitorTrigger(http.DefaultClient, cmd.String("access-token"), fmt.Sprintf("%d", id)); err != nil { + ch <- err + } + + }(id) + } + wg.Wait() + close(ch) // Close the channel when all workers have finished + + if len(ch) > 0 { + return cli.Exit("Some tests failed", 1) + } + fmt.Println("Test ran succesfully🔥") return nil }, Flags: []cli.Flag{ diff --git a/internal/whoami/whoami.go b/internal/whoami/whoami.go index 22ce612..3a3e0cf 100644 --- a/internal/whoami/whoami.go +++ b/internal/whoami/whoami.go @@ -27,6 +27,9 @@ func getWhoamiCmd(httpClient *http.Client, apiKey string) error { } defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return fmt.Errorf("Failed to get workspace information") + } body, _ := io.ReadAll(res.Body) var whoami Whoami err = json.Unmarshal(body, &whoami) @@ -47,7 +50,10 @@ func WhoamiCmd() *cli.Command { Usage: "Get your current workspace information", Action: func(ctx context.Context, cmd *cli.Command) error { fmt.Println("Your current workspace information") - getWhoamiCmd(http.DefaultClient, cmd.String("access-token")) + err := getWhoamiCmd(http.DefaultClient, cmd.String("access-token")) + if err != nil { + return cli.Exit("Failed to get workspace information", 1) + } return nil }, Flags: []cli.Flag{ diff --git a/openstatus.yml b/openstatus.yml deleted file mode 100644 index 607da62..0000000 --- a/openstatus.yml +++ /dev/null @@ -1,2 +0,0 @@ -tests: - - id: 1