diff --git a/.gitignore b/.gitignore index f1c181e..658bb4b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out + +# Build output +/github_status +/dist/ diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 61db75a..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,34 +0,0 @@ -pipeline{ - agent any - options { - buildDiscarder logRotator(artifactDaysToKeepStr: '30', artifactNumToKeepStr: '2', daysToKeepStr: '30', numToKeepStr: '10') - } - stages{ - stage("checkout"){ - steps{ - git 'https://github.com/mikemadden42/github_status.git' - } - } - stage("build"){ - steps{ - sh label: '', script: 'go build -ldflags="-s -w" -v -o github_status' - } - } - stage("archive"){ - steps{ - archiveArtifacts 'github_status' - } - } - } - post{ - always{ - echo "========always========" - } - success{ - echo "========pipeline executed successfully ========" - } - failure{ - echo "========pipeline execution failed========" - } - } -} \ No newline at end of file diff --git a/Makefile b/Makefile index d69c34f..c777c9d 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ build: check: golangci-lint run -release: +snapshot: goreleaser release --snapshot --clean -.PHONY: build check release +.PHONY: build check snapshot diff --git a/README.md b/README.md index d54d449..3176550 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,57 @@ # github_status -GitHub Status ![example workflow](https://github.com/mikemadden42/github_status/actions/workflows/go.yml/badge.svg) + +Print the current status of each GitHub component, from +[githubstatus.com](https://www.githubstatus.com). + +## Install + +```sh +go install github.com/mikemadden42/github_status@latest +``` + +## Usage + +```sh +github_status +``` + +``` +All Systems Operational + +Git Operations operational +Webhooks operational +API Requests operational +Issues operational +Pull Requests operational +Actions operational +Packages operational +Pages operational +Copilot operational +Codespaces operational +Copilot AI Model Providers operational +``` + +Statuses are colored: green for operational, yellow for degraded performance or +partial outage, red for major outage and blue for maintenance. + +### Scripting + +With `-check`, `github_status` exits with status 1 if any component is not +operational: + +```sh +github_status -check || echo "GitHub is having problems" +``` + +It also exits with status 1 if the status page can't be reached. + +## Development + +```sh +go test ./... +make check # golangci-lint +make build # goreleaser snapshot build into dist/ +make snapshot # full goreleaser snapshot release (local only, nothing is published) +``` diff --git a/TODO.md b/TODO.md index f9bc1a8..3a32f98 100644 --- a/TODO.md +++ b/TODO.md @@ -30,7 +30,7 @@ - [x] Use `json.NewDecoder(resp.Body).Decode` instead of `io.ReadAll` + `Unmarshal` - [x] Use the readable URL `https://www.githubstatus.com/api/v2/summary.json` - [x] Add a package comment (revive) -- [ ] Exit non-zero when any component isn't operational +- [x] Exit non-zero when any component isn't operational (with `-check`) ## Tests / CI @@ -45,7 +45,7 @@ ## Housekeeping -- [ ] Delete the Jenkinsfile unless Jenkins is in use (build-only, no tests, echo-only post blocks) -- [ ] `make release` uses `--snapshot`, so it never publishes. Rename the target or drop the flag -- [ ] Add `dist/` and `github_status` to `.gitignore` -- [ ] Expand the README with usage, install and example output +- [x] Delete the Jenkinsfile unless Jenkins is in use (build-only, no tests, echo-only post blocks) +- [x] `make release` uses `--snapshot`, so it never publishes. Renamed to `make snapshot` +- [x] Add `dist/` and `github_status` to `.gitignore` +- [x] Expand the README with usage, install and example output diff --git a/github_status.go b/github_status.go index 7ec3a2e..b4673c7 100644 --- a/github_status.go +++ b/github_status.go @@ -6,6 +6,7 @@ package main import ( "encoding/json" + "flag" "fmt" "io" "net/http" @@ -29,9 +30,16 @@ type summary struct { const summaryURL = "https://www.githubstatus.com/api/v2/summary.json" func main() { + check := flag.Bool("check", false, "exit with status 1 if any component is not operational") + flag.Parse() + s, err := fetch(summaryURL) checkErr(err) checkErr(render(os.Stdout, s)) + + if *check && !operational(s) { + os.Exit(1) + } } // fetch retrieves and decodes the status page summary at url. @@ -76,6 +84,17 @@ func render(w io.Writer, s summary) error { return nil } +// operational reports whether every component shown on the status page is +// operational. +func operational(s summary) bool { + for _, component := range s.Components { + if component.Showcase && component.Status != "operational" { + return false + } + } + return true +} + // statusColor returns the color used to display a component status. func statusColor(status string) *color.Color { switch status { diff --git a/github_status_test.go b/github_status_test.go index 39c3c77..81fbeae 100644 --- a/github_status_test.go +++ b/github_status_test.go @@ -117,3 +117,25 @@ func TestStatusColor(t *testing.T) { } } } + +func TestOperational(t *testing.T) { + s, err := fetch(serveFixture(t).URL) + if err != nil { + t.Fatal(err) + } + if operational(s) { + t.Error("fixture has an outage, want operational false") + } + + for i := range s.Components { + s.Components[i].Status = "operational" + } + if !operational(s) { + t.Error("all components operational, want operational true") + } + + s.Components[2].Status = "major_outage" // the non-showcase filler row + if !operational(s) { + t.Error("only a non-showcase component is down, want operational true") + } +}