From 2d27c0bae22db90406c6cdc3179a96358074a26e Mon Sep 17 00:00:00 2001 From: Italo Vietro Date: Thu, 12 Jul 2018 14:27:52 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + CONTRIBUTING | 66 ++++++++++++++++++++++++++++++ Gopkg.lock | 82 ++++++++++++++++++++++++++++++++++++++ Gopkg.toml | 62 ++++++++++++++++++++++++++++ Makefile | 63 +++++++++++++++++++++++++++++ README.md | 28 +++++++++++++ cmd/check.go | 41 +++++++++++++++++++ cmd/root.go | 43 ++++++++++++++++++++ cmd/version.go | 23 +++++++++++ goreleaser.yaml | 37 +++++++++++++++++ main.go | 15 +++++++ pkg/reachable/reachable.go | 66 ++++++++++++++++++++++++++++++ 12 files changed, 527 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/check.go create mode 100644 cmd/root.go create mode 100644 cmd/version.go create mode 100644 goreleaser.yaml create mode 100644 main.go create mode 100644 pkg/reachable/reachable.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000..2414cf5 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,66 @@ +# Contributing to Reachable + +:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: + +The following is a set of guidelines for contributing to Reachable and its packages, +which are hosted on [Github](https://github.com/italolelis) on GitHub. +These are just guidelines, not rules. Use your best judgment, and feel free to propose changes +to this document in a pull request. + +## Code of Conduct + +This project adheres to the Contributor Covenant [code of conduct](CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. +Please report unacceptable behavior to [italolelis@gmail.com](mailto:italolelis@gmail.com). + +We accept contributions via Pull Requests on [Github](https://github.com/italolelis/reachable). + +## How Can I Contribute? + +### Reporting Bugs + +This section guides you through submitting a bug report for Reachable. Following these guidelines helps maintainers +and the community understand your report :pencil:, reproduce the behavior :computer: :computer:, and find related +reports :mag_right:. + +Before creating bug reports, please check if the bug was already reported before as you might find out that you don't +need to create one. When you are creating a bug report, please [include as many details as possible](#how-do-i-submit-a-good-bug-report). + +#### How Do I Submit A (Good) Bug Report? + +Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). Create an issue on provide the following information. + +Explain the problem and include additional details to help maintainers reproduce the problem: + +* **Use a clear and descriptive title** for the issue to identify the problem. +* **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you started Reachable, +e.g. which command exactly you used in the terminal. When listing steps, **don't just say what you did, but explain how you did it**. +* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. +If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). +* **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. +* **Explain which behavior you expected to see instead and why.** + +Include details about your configuration and environment: + +* **Which version of Reachable are you using?** +* **What's the name and version of the OS you're using**? + +### Your First Code Contribution + +Unsure where to begin contributing to Reachable? You can start by looking through these `beginner` and `help-wanted` issues: + +* [Beginner issues][beginner] - issues which should only require a few lines of code, and a test or two. +* [Help wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues. + +Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have. + +### Pull Requests + +* Include screenshots and animated GIFs in your pull request whenever possible. +* Follow the [Go](https://github.com/golang/go/wiki/CodeReviewComments) styleguides. +* Include thoughtfully-worded, well-structured tests. +* Document new code +* End files with a newline. + + +Happy Coding! diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..0976a9d --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,82 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/afex/hystrix-go" + packages = [ + "hystrix", + "hystrix/metric_collector", + "hystrix/rolling" + ] + revision = "fa1af6a1f4f56e0e50d427fe901cd604d8c6fb8a" + +[[projects]] + name = "github.com/gojektech/heimdall" + packages = ["."] + revision = "f19a05cb7eebd406539137b5ddc0c49c1fb4815c" + version = "v3.0.1" + +[[projects]] + name = "github.com/gojektech/valkyrie" + packages = ["."] + revision = "6aee720afcdffc337029305c126e0079491063f0" + version = "v1.0" + +[[projects]] + name = "github.com/inconshreveable/mousetrap" + packages = ["."] + revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" + version = "v1.0" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + name = "github.com/sirupsen/logrus" + packages = ["."] + revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" + version = "v1.0.5" + +[[projects]] + name = "github.com/spf13/cobra" + packages = ["."] + revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" + version = "v0.0.3" + +[[projects]] + name = "github.com/spf13/pflag" + packages = ["."] + revision = "583c0c0531f06d5278b7d917446061adc344b5cd" + version = "v1.0.1" + +[[projects]] + name = "github.com/tcnksm/go-httpstat" + packages = ["."] + revision = "ae0d799e7ee65d3dc46d7272368daa830aef6c5a" + version = "v0.2.0" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = [ + "unix", + "windows" + ] + revision = "1b2967e3c290b7c545b3db0deeda16e9be4f98a2" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "01524409a91c245bf9c27279ef83c85059f21664c5fb082b6b3285a17d76ad51" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..c3ae9ad --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,62 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/gojektech/heimdall" + version = "3.0.1" + +[[constraint]] + branch = "master" + name = "github.com/hashicorp/errwrap" + +[[constraint]] + branch = "master" + name = "github.com/mitchellh/go-homedir" + +[[constraint]] + name = "github.com/sirupsen/logrus" + version = "1.0.5" + +[[constraint]] + name = "github.com/spf13/cobra" + version = "0.0.3" + +[[constraint]] + name = "github.com/spf13/viper" + version = "1.0.2" + +[prune] + go-tests = true + unused-packages = true + +[[constraint]] + branch = "master" + name = "golang.org/x/sync" + +[[constraint]] + name = "github.com/tcnksm/go-httpstat" + version = "0.2.0" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cec952a --- /dev/null +++ b/Makefile @@ -0,0 +1,63 @@ +NO_COLOR=\033[0m +OK_COLOR=\033[32;01m +ERROR_COLOR=\033[31;01m +WARN_COLOR=\033[33;01m + +# The import path is the unique absolute name of your repository. +# All subpackages should always be imported as relative to it. +# If you change this, run `make clean`. +PKG_SRC := github.com/italolelis/reachable + +.PHONY: all clean deps build + +all: clean deps test build + +deps: + @echo "$(OK_COLOR)==> Installing dependencies$(NO_COLOR)" + @go get -u github.com/golang/dep/cmd/dep + @go get -u github.com/golang/lint/golint + @dep ensure -vendor-only + +build: + @echo "$(OK_COLOR)==> Building... $(NO_COLOR)" + CGO_ENABLED=0 go build -ldflags "-s -w" -ldflags "-X cmd.version=$(VERSION)" -o "dist/reachable" $(PKG_SRC) + +test: lint format vet + @echo "$(OK_COLOR)==> Running tests$(NO_COLOR)" + @go test -v -cover ./... + +format: + @echo "$(OK_COLOR)==> checking code formating with 'gofmt' tool$(NO_COLOR)" + @gofmt -l -s cmd pkg | grep ".*\.go"; if [ "$$?" = "0" ]; then exit 1; fi + +vet: + @echo "$(OK_COLOR)==> checking code correctness with 'go vet' tool$(NO_COLOR)" + @go vet ./... + +lint: tools.golint + @echo "$(OK_COLOR)==> checking code style with 'golint' tool$(NO_COLOR)" + @go list ./... | xargs -n 1 golint -set_exit_status + +clean: + @echo "$(OK_COLOR)==> Cleaning project$(NO_COLOR)" + @go clean + @rm -rf bin $GOPATH/bin + +#--------------- +#-- tools +#--------------- + +.PHONY: tools tools.dep tools.golint +tools: tools.dep tools.golint + +tools.golint: + @command -v golint >/dev/null ; if [ $$? -ne 0 ]; then \ + echo "--> installing golint"; \ + go get github.com/golang/lint/golint; \ + fi + +tools.dep: + @command -v dep >/dev/null ; if [ $$? -ne 0 ]; then \ + echo "--> installing dep"; \ + @go get -u github.com/golang/dep/cmd/dep; \ + fi \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fd2774 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Reachable + +> A CLI tool to check if a domain is up + +Welcome to reachable a CLI tool that helps you to check if a domain is up or down. + +## Installation + +Just go the [releases](https://github.com/italolelis/reachable/releases) and download the latest one for your platform. + +Just place the binary in your $PATH and you are good to go. + +## Usage + +``` +reachable [command] [--flags] +``` + +### Commands + +| Command | Description | +|--------------------------|--------------------------------------| +| `reachable check [--flags]` | Checks if a domain is reachable | +| `reachable version` | Prints the version information | + +# Contributing + +To start contributing, please check [CONTRIBUTING](CONTRIBUTING) diff --git a/cmd/check.go b/cmd/check.go new file mode 100644 index 0000000..9b281c7 --- /dev/null +++ b/cmd/check.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "context" + "time" + + "github.com/italolelis/reachable/pkg/log" + "github.com/italolelis/reachable/pkg/reachable" + "github.com/spf13/cobra" +) + +// NewCheckCmd creates a new check command +func NewCheckCmd(ctx context.Context, timeout time.Duration) *cobra.Command { + return &cobra.Command{ + Use: "check", + Short: "Checks if a domain is reachable", + Aliases: []string{"v"}, + Run: func(cmd *cobra.Command, args []string) { + logger := log.WithContext(ctx) + + result, err := reachable.IsReachable(ctx, args[0], timeout) + if err != nil { + logger.Debug(err.Error()) + logger.Error("Not Reachable!") + return + } + + logger.Debugf("Domain %s", result.Domain) + logger.Debugf("IP %s", result.IP) + logger.Debugf("Status Code %d", result.StatusCode) + logger.Debugf("DNS Lookup %d ms", int(result.Response.DNSLookup/time.Millisecond)) + logger.Debugf("TCP Connection %d ms", int(result.Response.TCPConnection/time.Millisecond)) + logger.Debugf("TLS Handshake %d ms", int(result.Response.TLSHandshake/time.Millisecond)) + logger.Debugf("Server Processing %d ms", int(result.Response.ServerProcessing/time.Millisecond)) + logger.Debugf("Content Transfer %d ms", int(result.Response.ContentTransfer(time.Now())/time.Millisecond)) + logger.Debugf("Total Time %d ms", int(result.Response.Total(time.Now())/time.Millisecond)) + + logger.Info("Reachable!") + }, + } +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..9aa1949 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,43 @@ +package cmd + +import ( + "context" + "time" + + "github.com/italolelis/reachable/pkg/log" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +type ( + // RootOptions represents the ahoy global options + RootOptions struct { + timeout time.Duration + verbose bool + } +) + +// NewRootCmd creates the root command +func NewRootCmd() *cobra.Command { + opts := RootOptions{} + ctx := log.NewContext(context.Background()) + + cmd := cobra.Command{ + Use: "reachable", + Short: "Reachable is a CLI tool to check if a domain is up", + PersistentPreRun: func(ccmd *cobra.Command, args []string) { + if opts.verbose { + log.WithContext(context.Background()).SetLevel(logrus.DebugLevel) + } + }, + } + + cmd.PersistentFlags().DurationVarP(&opts.timeout, "timeout", "t", 30*time.Second, "Defines a timeout") + cmd.PersistentFlags().BoolVarP(&opts.verbose, "verbose", "v", false, "Make the operation more talkative") + + // Aggregates Root commands + cmd.AddCommand(NewCheckCmd(ctx, opts.timeout)) + cmd.AddCommand(NewVersionCmd(ctx)) + + return &cmd +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..38238b9 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,23 @@ +package cmd + +import ( + "context" + + "github.com/italolelis/reachable/pkg/log" + "github.com/spf13/cobra" +) + +var version = "0.0.0-dev" + +// NewVersionCmd creates a new version command +func NewVersionCmd(ctx context.Context) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print the version information", + Aliases: []string{"v"}, + Run: func(cmd *cobra.Command, args []string) { + logger := log.WithContext(ctx) + logger.Infof("reachable %s", version) + }, + } +} diff --git a/goreleaser.yaml b/goreleaser.yaml new file mode 100644 index 0000000..fafffd1 --- /dev/null +++ b/goreleaser.yaml @@ -0,0 +1,37 @@ +project_name: reachable +builds: + - main: main.go + goos: + - windows + - darwin + - linux + goarch: + - amd64 + + env: + - CGO_ENABLED=0 + + ldflags: -s -w -X github.com/italolelis/reachable/cmd.version={{.Version}} + +archive: + format: binary + +brew: + # Reporitory to push the tap to. + github: + owner: italolelis + name: homebrew-reachable + + # Git author used to commit to the repository. + # Defaults are shown. + commit_author: + name: italolelis + email: italolelis@gmail.com + + # Your app's homepage. + # Default is empty. + homepage: "https://github.com/italolelis/reachable" + + # Your app's description. + # Default is empty. + description: "A CLI tool to check if a domain is up" diff --git a/main.go b/main.go new file mode 100644 index 0000000..d2d66f9 --- /dev/null +++ b/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "os" + + "github.com/italolelis/reachable/cmd" +) + +func main() { + if err := cmd.NewRootCmd().Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/pkg/reachable/reachable.go b/pkg/reachable/reachable.go new file mode 100644 index 0000000..b6cedd2 --- /dev/null +++ b/pkg/reachable/reachable.go @@ -0,0 +1,66 @@ +package reachable + +import ( + "context" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "time" + + "github.com/gojektech/heimdall" + httpstat "github.com/tcnksm/go-httpstat" +) + +// Reachable represents the response for a domain check +type Reachable struct { + Domain string + Port string + IP string + StatusCode int + Response httpstat.Result +} + +// IsReachable checks if a domain is reachable +func IsReachable(ctx context.Context, domain string, timeout time.Duration) (*Reachable, error) { + var result httpstat.Result + + u, err := url.Parse(domain) + if err != nil { + return nil, err + } + + if u.Scheme == "" { + u.Scheme = "http" + } + + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + return nil, err + } + + ctx = httpstat.WithHTTPStat(ctx, &result) + req = req.WithContext(ctx) + + c := heimdall.NewHTTPClient(timeout) + res, err := c.Do(req) + if err != nil { + return nil, err + } + + if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { + log.Fatal(err) + } + res.Body.Close() + result.End(time.Now()) + + r := Reachable{ + Domain: res.Request.URL.Hostname(), + Port: res.Request.URL.Port(), + StatusCode: res.StatusCode, + Response: result, + } + + return &r, nil +}