Update for Prometheus Community

Add standard Prometheus build setup
* CircleCI config
* Makefile
* Go modules
* Golang-CI Lint
* promu config
* Remove /vendor
* Remove mage build
* Update READMEs

https://github.com/prometheus-community/postgres_exporter/issues/478

Signed-off-by: Ben Kochie <superq@gmail.com>
This commit is contained in:
Ben Kochie 2021-02-19 10:52:01 +01:00
parent 8531abac46
commit b67b69acd3
No known key found for this signature in database
GPG Key ID: C646B23C9E3245F1
1455 changed files with 682 additions and 529253 deletions

57
.circleci/config.yml Normal file
View File

@ -0,0 +1,57 @@
---
version: 2.1
orbs:
prometheus: prometheus/prometheus@0.8.0
executors:
# This must match .promu.yml.
golang:
docker:
- image: circleci/golang:1.15
jobs:
test:
executor: golang
steps:
- prometheus/setup_environment
- run: make
- prometheus/store_artifact:
file: postgres_exporter
workflows:
version: 2
postgres_exporter:
jobs:
- test:
filters:
tags:
only: /.*/
- prometheus/build:
name: build
filters:
tags:
only: /.*/
- prometheus/publish_master:
context: org-context
docker_hub_organization: prometheuscommunity
quay_io_organization: prometheuscommunity
requires:
- test
- build
filters:
branches:
only: master
- prometheus/publish_release:
context: org-context
docker_hub_organization: prometheuscommunity
quay_io_organization: prometheuscommunity
requires:
- test
- build
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/

View File

@ -1,2 +0,0 @@
*
!bin/

1
.gitignore vendored
View File

@ -19,3 +19,4 @@
/.metrics.*.added
/.metrics.*.removed
/tools/src
/vendor

10
.golangci.yml Normal file
View File

@ -0,0 +1,10 @@
---
issues:
exclude-rules:
- path: _test.go
linters:
- errcheck
linters-settings:
errcheck:
exclude: scripts/errcheck_excludes.txt

19
.promu.yml Normal file
View File

@ -0,0 +1,19 @@
go:
# This must match .circle/config.yml.
version: 1.15
repository:
path: github.com/prometheus-community/postgres_exporter
build:
binaries:
- name: postgres_exporter
path: ./cmd/postgres_exporter
flags: -a -tags 'netgo static_build'
ldflags: |
-X github.com/prometheus/common/version.Version={{.Version}}
-X github.com/prometheus/common/version.Revision={{.Revision}}
-X github.com/prometheus/common/version.Branch={{.Branch}}
-X github.com/prometheus/common/version.BuildUser={{user}}@{{host}}
-X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}}
tarball:
files:
- LICENSE

View File

@ -1,46 +0,0 @@
sudo: required
services:
- docker
language: go
go:
- '1.11'
before_install:
- go get -v github.com/mattn/goveralls
- sudo wget -O /usr/local/bin/p2 https://github.com/wrouesnel/p2cli/releases/download/r4/p2
&& sudo chmod +x /usr/local/bin/p2
- sudo wget -O /usr/local/bin/docker-compose https://github.com/docker/compose/releases/download/1.9.0-rc4/docker-compose-Linux-x86_64
&& sudo chmod +x /usr/local/bin/docker-compose
- sudo apt-get update && sudo apt-get install postgresql-client-common
script:
- "./gh-assets-clone.sh"
- go run mage.go -v all
- "$HOME/gopath/bin/goveralls -coverprofile=cover.out -service=travis-ci"
- go run mage.go docker
after_success:
- docker login -u $DOCKER_USER -p $DOCKER_PASS
- if [ ! -z "$TRAVIS_TAG" ]; then docker tag wrouesnel/postgres_exporter:latest wrouesnel/postgres_exporter:$TRAVIS_TAG
; docker push wrouesnel/postgres_exporter:$TRAVIS_TAG ; fi
- if [ "$TRAVIS_BRANCH" == "master" ]; then docker push wrouesnel/postgres_exporter
; fi
- "./postgres-metrics-get-changes.sh .assets-branch/metriclists"
- if [ "$TRAVIS_BRANCH" == "master" ]; then ./gh-metrics-push.sh ; fi
env:
global:
- DOCKER_USER=wrouesnel
- GIT_ASSETS_BRANCH=assets
- secure: sl1d85bipYhHlHTZ4fwkWrZ07px+lPMQrKPaiyQ9i5tylQAcMqwDroK0pb5HIyIl6PEx72D5atQWnEqluA/0rFt3SxqxtvT+wj6CPmmZfh2fUSol7I07QzAsi95d7q0fg2mStDdfs134Uu+JjxGKEGRu2SL3Zq+LKpaNPtIZVBqrCYYAySLiEJx+DEOfwt1ktn/qHapV5d5FYdfd7trfV411NITyA8AGk6Gy0HztRDGbfcoLOsM+CnVi1p59uUL9ck/hL2DbsB44qDKeWQaruMLwWNDETu+EVwHlDEHGBPb+wdDALnW+Ts3CAUpuGXftHV35XLLbH7NXOnS6QiH938ycfPf3INY51lV7cL6bNtFWDKMAIcPf4wQO2ts4qFhuiUeFdo7qrC6uEI5Fy/sELBgWl4O2opVY3Tf8s8OO/DSb4Cxy6solKgaETkl6EcShaEj7H/Cn7vT0+SLKCpSQlvVQXDLGg6eZTyBA+OWNElE0UvWV7znxWBlke+9NARIl4FcB/SY4A6v1ztpandHWMjNLLxZyVxFEswfU9hvf0qL9SW38OJ5cIK8pvmH2QWG7Xg/j0B3o7SHMdsM+pcSwrzsM6OENgvxPNBb/DinmMyQKxTCVcVmMo7uIS89RIylvN79E8U6NagdFkiLfa3xEHq8zCzEkHi3bsLRvytgT2X0=
- secure: 1JL8KcgkLueQ2DLL81UMYzIHX3qm1TjcO40QL2ZOfdirFWBshOiTwTXeWj5qZaGBzoVV5ezhyZaBY+t3/pObslm20ERce879hEw+TSnKN30wfBqNyv2r7rfsbKkXauultb8RNu9y/9XS0DCEyGdSTQh9UaCa4z6ulu39hffDddrGQjwW1P2gT3Npu1cDYd1iSO36rrA6yXjaoN8OW8U4znKVjOGnarxxFnXJkiYv2PfIrZA6BpL3d0syJtWDyr1G+B48oK9VK+fBV9K0G0E67fJvqB3ANXN3D41il3S+cs8Ulcd7hF+LWxpMsP2r1/XHYSDw3Iiz0QFKKzoyxNdipvdjAVDxrWylyLnmTBYzXk41kRv88mKVLBQM1dbzsLXYcsE2pgIZxxq9OHGZ5CUJ8t0oz5D9oXMUy4QOMQ36jZdvD048aB7DGp4EF2J7ILIhUZrHHErOlXotnsYvNMvamNwqB5Jg4NC+y5QHxERJ+HK5oPrLy+iCb2kmWatSB6vO5OeX/F7IRiqtZghJRddEeMdQ1a6H0GeV1BF7Hx8j3TPMJ66qSAb0RA1lQQCN4l+/YMEWmQD8amf1O5NY116waf+Co4qkvt3c4QctQOMwu3Ra7uLlp6GG61OmHhPTCGSv/LZp6CVtROLY5IltKv7qBzksjvXkO1SzhJOxi0JkZmg=
branches:
except:
- assets
deploy:
skip_cleanup: true
provider: releases
api_key:
secure: rwlge/Rs3wnWyfKRhD9fd5GviVe0foYUp20DY3AjKdDjhtwScA1EeR9QHOkB3raze52en0+KkpqlLCWbt3q4CRT7+ku1DNKhd6VWALdTZ1RPJYvNlU6CKJdRnWUJsECmSBsShXlbiYR8axqNVedzFPFGKzS9gYlFN6rr7pez/JZhxqucopZ6I+TkRHMELrFXyQK7/Y2bNRCLC4a+rGsjKeLLtYXbRXCmS0G4BSJEBRk7d69fIRzBApCMfrcLftgHzPuPth616yyUusQSCQYvaZ5tlwrPP8/E0wG3SVJVeDCMuDOSBZ9M6vNzR8W8VR/hxQamegn1OQgC5kNOaLZCTcJ5xguRouqb+FNFBqrd/Zi6vESo7RiVLULawzwxkh9sIPa3WZYDb3VK/Z/cpggUeR7wAu0S5ZYEvJHRefIZpqofZEHzDE3Blqp5yErz05e/zmjpd6HHK3f/UHmRRYfbulkvGT3aL/dlq5GcFvuxVC/vTL2VPvg9cGbqtf7PakC5IhoHpDs35tOyLxifOBLHvkwtGSxEfsCohIG8Hz2XFD83EsxgOiKSXVPLNd6yxjdqZj7OeAKFFU3bzGndnRbDIXaf987IN1imgUtP6wegfImoRStqxN4gEwwIMFsZCF86Ug4eLhlajLbWhudriDxDPBM/F9950aVxLwmWh9l5cRI=
file_glob: true
file: release/*
on:
tags: true
branch: master
repo: wrouesnel/postgres_exporter

View File

@ -1,12 +1,12 @@
FROM debian:10-slim
RUN useradd -u 20001 postgres_exporter
ARG ARCH="amd64"
ARG OS="linux"
FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest
LABEL maintainer="The Prometheus Authors <prometheus-developers@googlegroups.com>"
USER postgres_exporter
ARG ARCH="amd64"
ARG OS="linux"
COPY .build/${OS}-${ARCH}/postgres_exporter /bin/postgres_exporter
ARG binary
COPY $binary /postgres_exporter
EXPOSE 9187
ENTRYPOINT [ "/postgres_exporter" ]
EXPOSE 9187
USER nobody
ENTRYPOINT [ "/bin/postgres_exporter" ]

10
Makefile Normal file
View File

@ -0,0 +1,10 @@
# Ensure that 'all' is the default target otherwise it will be the first target from Makefile.common.
all::
# Needs to be defined before including Makefile.common to auto-generate targets
DOCKER_ARCHS ?= amd64 armv7 arm64 ppc64le
DOCKER_REPO ?= prometheuscommunity
include Makefile.common
DOCKER_IMAGE_NAME ?= postgres-exporter

View File

@ -69,12 +69,21 @@ else
GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
endif
PROMU_VERSION ?= 0.4.0
GOTEST := $(GO) test
GOTEST_DIR :=
ifneq ($(CIRCLE_JOB),)
ifneq ($(shell which gotestsum),)
GOTEST_DIR := test-results
GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
endif
endif
PROMU_VERSION ?= 0.7.0
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
GOLANGCI_LINT :=
GOLANGCI_LINT_OPTS ?=
GOLANGCI_LINT_VERSION ?= v1.16.0
GOLANGCI_LINT_VERSION ?= v1.36.0
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
# windows isn't included here because of the path separator being different.
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
@ -86,6 +95,8 @@ endif
PREFIX ?= $(shell pwd)
BIN_DIR ?= $(shell pwd)
DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
DOCKERFILE_PATH ?= ./Dockerfile
DOCKERBUILD_CONTEXT ?= ./
DOCKER_REPO ?= prom
DOCKER_ARCHS ?= amd64
@ -139,15 +150,29 @@ else
$(GO) get $(GOOPTS) -t ./...
endif
.PHONY: update-go-deps
update-go-deps:
@echo ">> updating Go dependencies"
@for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \
$(GO) get $$m; \
done
GO111MODULE=$(GO111MODULE) $(GO) mod tidy
ifneq (,$(wildcard vendor))
GO111MODULE=$(GO111MODULE) $(GO) mod vendor
endif
.PHONY: common-test-short
common-test-short:
common-test-short: $(GOTEST_DIR)
@echo ">> running short tests"
GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs)
GO111MODULE=$(GO111MODULE) $(GOTEST) -short $(GOOPTS) $(pkgs)
.PHONY: common-test
common-test:
common-test: $(GOTEST_DIR)
@echo ">> running all tests"
GO111MODULE=$(GO111MODULE) $(GO) test $(test-flags) $(GOOPTS) $(pkgs)
GO111MODULE=$(GO111MODULE) $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)
$(GOTEST_DIR):
@mkdir -p $@
.PHONY: common-format
common-format:
@ -199,7 +224,7 @@ endif
.PHONY: common-build
common-build: promu
@echo ">> building binaries"
GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX)
GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES)
.PHONY: common-tarball
common-tarball: promu
@ -210,19 +235,22 @@ common-tarball: promu
common-docker: $(BUILD_DOCKER_ARCHS)
$(BUILD_DOCKER_ARCHS): common-docker-%:
docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \
-f $(DOCKERFILE_PATH) \
--build-arg ARCH="$*" \
--build-arg OS="linux" \
.
$(DOCKERBUILD_CONTEXT)
.PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS)
common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)"
DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION)))
.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
.PHONY: common-docker-manifest
common-docker-manifest:
@ -247,7 +275,9 @@ proto:
ifdef GOLANGCI_LINT
$(GOLANGCI_LINT):
mkdir -p $(FIRST_GOPATH)/bin
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \
| sed -e '/install -d/d' \
| sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION)
endif
ifdef GOVENDOR

View File

@ -3,7 +3,7 @@
### When using postgres-exporter with Amazon Web Services' RDS, the
rolname "rdsadmin" and datname "rdsadmin" must be excluded.
I had success running docker container 'wrouesnel/postgres_exporter:latest'
I had success running docker container 'quay.io/prometheuscommunity/postgres-exporter:latest'
with queries.yaml as the PG_EXPORTER_EXTEND_QUERY_PATH. errors
mentioned in issue#335 appeared and I had to modify the
'pg_stat_statements' query with the following:
@ -24,7 +24,7 @@ Running postgres-exporter in a container like so:
-e PG_EXPORTER_DISABLE_DEFAULT_METRICS=true \
-e PG_EXPORTER_DISABLE_SETTINGS_METRICS=true \
-e PG_EXPORTER_EXTEND_QUERY_PATH='/var/lib/postgresql/queries.yaml' \
wrouesnel/postgres_exporter
quay.io/prometheuscommunity/postgres-exporter
```
### Expected changes to RDS:

View File

@ -1,7 +1,7 @@
[![Build Status](https://travis-ci.org/wrouesnel/postgres_exporter.svg?branch=master)](https://travis-ci.org/wrouesnel/postgres_exporter)
[![Coverage Status](https://coveralls.io/repos/github/wrouesnel/postgres_exporter/badge.svg?branch=master)](https://coveralls.io/github/wrouesnel/postgres_exporter?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/wrouesnel/postgres_exporter)](https://goreportcard.com/report/github.com/wrouesnel/postgres_exporter)
[![Docker Pulls](https://img.shields.io/docker/pulls/wrouesnel/postgres_exporter.svg)](https://hub.docker.com/r/wrouesnel/postgres_exporter/tags)
[![Build Status](https://circleci.com/gh/prometheus-community/postgres_exporter.svg?style=svg)](https://circleci.com/gh/prometheus-community/postgres_exporter)
[![Coverage Status](https://coveralls.io/repos/github/prometheus-community/postgres_exporter/badge.svg?branch=master)](https://coveralls.io/github/prometheus-community/postgres_exporter?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus-community/postgres_exporter)](https://goreportcard.com/report/github.com/prometheus-community/postgres_exporter)
[![Docker Pulls](https://img.shields.io/docker/pulls/prometheuscommunity/postgres-exporter.svg)](https://hub.docker.com/r/prometheuscommunity/postgres-exporter/tags)
# PostgreSQL Server Exporter
@ -15,31 +15,30 @@ This package is available for Docker:
# Start an example database
docker run --net=host -it --rm -e POSTGRES_PASSWORD=password postgres
# Connect to it
docker run --net=host -e DATA_SOURCE_NAME="postgresql://postgres:password@localhost:5432/postgres?sslmode=disable" wrouesnel/postgres_exporter
docker run \
--net=host \
-e DATA_SOURCE_NAME="postgresql://postgres:password@localhost:5432/postgres?sslmode=disable" \
quay.io/prometheuscommunity/postgres-exporter
```
## Building and running
The build system is based on [Mage](https://magefile.org)
git clone https://github.com/prometheus-community/postgres_exporter.git
cd postgres_exporter
make build
./postgres_exporter <flags>
The default make file behavior is to build the binary:
```
$ go get github.com/wrouesnel/postgres_exporter
$ cd ${GOPATH-$HOME/go}/src/github.com/wrouesnel/postgres_exporter
$ go run mage.go binary
$ export DATA_SOURCE_NAME="postgresql://login:password@hostname:port/dbname"
$ ./postgres_exporter <flags>
```
To build the Docker image:
To build the dockerfile, run `go run mage.go docker`.
make promu
promu crossbuild -p linux/amd64 -p linux/armv7 -p linux/amd64 -p linux/ppc64le
make docker
This will build the docker image as `wrouesnel/postgres_exporter:latest`. This
is a minimal docker image containing *just* postgres_exporter. By default no SSL
certificates are included, if you need to use SSL you should either bind-mount
`/etc/ssl/certs/ca-certificates.crt` or derive a new image containing them.
This will build the docker image as `prometheuscommunity/postgres_exporter:${branch}`.
### Vendoring
Package vendoring is handled with [`govendor`](https://github.com/kardianos/govendor)
Package vendoring is handled with Go modules.
### Flags
@ -277,10 +276,3 @@ GRANT SELECT ON postgres_exporter.pg_stat_statements TO postgres_exporter;
> ```
> DATA_SOURCE_NAME=postgresql://postgres_exporter:password@localhost:5432/postgres?sslmode=disable
> ```
# Hacking
* To build a copy for your current architecture run `go run mage.go binary`.
This will create a symlink to the just built binary in the root directory.
* To build release tar balls run `go run mage.go release`.
* Build system is a bit temperamental at the moment since the conversion to mage - I am working on getting it
to be a perfect out of the box experience, but am time-constrained on it at the moment.

View File

@ -1,3 +1,16 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (

View File

@ -1,3 +1,16 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !integration
package main

View File

@ -1,3 +1,16 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
@ -1260,7 +1273,7 @@ func newDesc(subsystem, name, help string, labels prometheus.Labels) *prometheus
}
func queryDatabases(server *Server) ([]string, error) {
rows, err := server.db.Query("SELECT datname FROM pg_database WHERE datallowconn = true AND datistemplate = false AND datname != current_database()") // nolint: safesql
rows, err := server.db.Query("SELECT datname FROM pg_database WHERE datallowconn = true AND datistemplate = false AND datname != current_database()")
if err != nil {
return nil, fmt.Errorf("Error retrieving databases: %v", err)
}
@ -1299,9 +1312,9 @@ func queryNamespaceMapping(server *Server, namespace string, mapping MetricMapNa
if !found {
// I've no idea how to avoid this properly at the moment, but this is
// an admin tool so you're not injecting SQL right?
rows, err = server.db.Query(fmt.Sprintf("SELECT * FROM %s;", namespace)) // nolint: gas, safesql
rows, err = server.db.Query(fmt.Sprintf("SELECT * FROM %s;", namespace)) // nolint: gas
} else {
rows, err = server.db.Query(query) // nolint: safesql
rows, err = server.db.Query(query)
}
if err != nil {
return []prometheus.Metric{}, []error{}, fmt.Errorf("Error running query on database %q: %s %v", server, namespace, err)

View File

@ -1,3 +1,16 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// These are specialized integration tests. We only build them when we're doing
// a lot of additional work to keep the external docker environment they require
// working.
@ -75,7 +88,7 @@ func (s *IntegrationSuite) TestAllNamespacesReturnResults(c *C) {
}
// TestInvalidDsnDoesntCrash tests that specifying an invalid DSN doesn't crash
// the exporter. Related to https://github.com/wrouesnel/postgres_exporter/issues/93
// the exporter. Related to https://github.com/prometheus-community/postgres_exporter/issues/93
// although not a replication of the scenario.
func (s *IntegrationSuite) TestInvalidDsnDoesntCrash(c *C) {
// Setup a dummy channel to consume metrics

View File

@ -1,3 +1,16 @@
// Copyright 2021 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !integration
package main

14
go.mod Normal file
View File

@ -0,0 +1,14 @@
module github.com/prometheus-community/postgres_exporter
go 1.14
require (
github.com/blang/semver v3.5.1+incompatible
github.com/lib/pq v1.9.0
github.com/prometheus/client_golang v1.9.0
github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.17.0
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gopkg.in/yaml.v2 v2.4.0
)

432
go.sum Normal file
View File

@ -0,0 +1,432 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.17.0 h1:kDIZLI74SS+3tedSvEkykgBkD7txMxaJAPj8DtJUKYA=
github.com/prometheus/common v0.17.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

11
mage.go
View File

@ -1,11 +0,0 @@
// +build ignore
package main
import (
"os"
"github.com/magefile/mage/mage"
)
func main() { os.Exit(mage.Main()) }

View File

@ -1,788 +0,0 @@
// +build mage
// Self-contained go-project magefile.
// nolint: deadcode
package main
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"runtime"
"strings"
"time"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
"github.com/magefile/mage/target"
"errors"
"math/bits"
"strconv"
"github.com/mholt/archiver"
)
var curDir = func() string {
name, _ := os.Getwd()
return name
}()
const constCoverageDir = ".coverage"
const constToolDir = "tools"
const constBinDir = "bin"
const constReleaseDir = "release"
const constCmdDir = "cmd"
const constCoverFile = "cover.out"
const constAssets = "assets"
const constAssetsGenerated = "assets/generated"
var coverageDir = mustStr(filepath.Abs(path.Join(curDir, constCoverageDir)))
var toolDir = mustStr(filepath.Abs(path.Join(curDir, constToolDir)))
var binDir = mustStr(filepath.Abs(path.Join(curDir, constBinDir)))
var releaseDir = mustStr(filepath.Abs(path.Join(curDir, constReleaseDir)))
var cmdDir = mustStr(filepath.Abs(path.Join(curDir, constCmdDir)))
var assetsGenerated = mustStr(filepath.Abs(path.Join(curDir, constAssetsGenerated)))
// Calculate file paths
var toolsGoPath = toolDir
var toolsSrcDir = mustStr(filepath.Abs(path.Join(toolDir, "src")))
var toolsBinDir = mustStr(filepath.Abs(path.Join(toolDir, "bin")))
var toolsVendorDir = mustStr(filepath.Abs(path.Join(toolDir, "vendor")))
var outputDirs = []string{binDir, releaseDir, toolsGoPath, toolsBinDir,
toolsVendorDir, assetsGenerated, coverageDir}
var toolsEnv = map[string]string{"GOPATH": toolsGoPath}
var containerName = func() string {
if name := os.Getenv("CONTAINER_NAME"); name != "" {
return name
}
return "wrouesnel/postgres_exporter:latest"
}()
type Platform struct {
OS string
Arch string
BinSuffix string
}
func (p *Platform) String() string {
return fmt.Sprintf("%s-%s", p.OS, p.Arch)
}
func (p *Platform) PlatformDir() string {
platformDir := path.Join(binDir, fmt.Sprintf("%s_%s_%s", productName, versionShort, p.String()))
return platformDir
}
func (p *Platform) PlatformBin(cmd string) string {
platformBin := fmt.Sprintf("%s%s", cmd, p.BinSuffix)
return path.Join(p.PlatformDir(), platformBin)
}
func (p *Platform) ArchiveDir() string {
return fmt.Sprintf("%s_%s_%s", productName, versionShort, p.String())
}
func (p *Platform) ReleaseBase() string {
return path.Join(releaseDir, fmt.Sprintf("%s_%s_%s", productName, versionShort, p.String()))
}
// Supported platforms
var platforms []Platform = []Platform{
{"linux", "amd64", ""},
{"linux", "386", ""},
{"linux", "arm64", ""},
{"linux", "mips64le", ""},
{"darwin", "amd64", ""},
{"darwin", "386", ""},
{"windows", "amd64", ".exe"},
{"windows", "386", ".exe"},
{"freebsd", "amd64", ""},
}
// productName can be overridden by environ product name
var productName = func() string {
if name := os.Getenv("PRODUCT_NAME"); name != "" {
return name
}
name, _ := os.Getwd()
return path.Base(name)
}()
// Source files
var goSrc []string
var goDirs []string
var goPkgs []string
var goCmds []string
var branch = func() string {
if v := os.Getenv("BRANCH"); v != "" {
return v
}
out, _ := sh.Output("git", "rev-parse", "--abbrev-ref", "HEAD")
return out
}()
var buildDate = func() string {
if v := os.Getenv("BUILDDATE"); v != "" {
return v
}
return time.Now().Format("2006-01-02T15:04:05-0700")
}()
var revision = func() string {
if v := os.Getenv("REVISION"); v != "" {
return v
}
out, _ := sh.Output("git", "rev-parse", "HEAD")
return out
}()
var version = func() string {
if v := os.Getenv("VERSION"); v != "" {
return v
}
out, _ := sh.Output("git", "describe", "--dirty")
if out == "" {
return "v0.0.0"
}
return out
}()
var versionShort = func() string {
if v := os.Getenv("VERSION_SHORT"); v != "" {
return v
}
out, _ := sh.Output("git", "describe", "--abbrev=0")
if out == "" {
return "v0.0.0"
}
return out
}()
var concurrency = func() int {
if v := os.Getenv("CONCURRENCY"); v != "" {
pv, err := strconv.ParseUint(v, 10, bits.UintSize)
if err != nil {
panic(err)
}
return int(pv)
}
return runtime.NumCPU()
}()
var linterDeadline = func() time.Duration {
if v := os.Getenv("LINTER_DEADLINE"); v != "" {
d, _ := time.ParseDuration(v)
if d != 0 {
return d
}
}
return time.Second * 60
}()
func Log(args ...interface{}) {
if mg.Verbose() {
fmt.Println(args...)
}
}
func init() {
// Set environment
os.Setenv("PATH", fmt.Sprintf("%s:%s", toolsBinDir, os.Getenv("PATH")))
Log("Build PATH: ", os.Getenv("PATH"))
Log("Concurrency:", concurrency)
goSrc = func() []string {
results := new([]string)
filepath.Walk(".", func(relpath string, info os.FileInfo, err error) error {
// Ensure absolute path so globs work
path, err := filepath.Abs(relpath)
if err != nil {
panic(err)
}
// Look for files
if info.IsDir() {
return nil
}
// Exclusions
for _, exclusion := range []string{toolDir, binDir, releaseDir, coverageDir} {
if strings.HasPrefix(path, exclusion) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
}
if strings.Contains(path, "/vendor/") {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
if strings.Contains(path, ".git") {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(path, ".go") {
return nil
}
*results = append(*results, path)
return nil
})
return *results
}()
goDirs = func() []string {
resultMap := make(map[string]struct{})
for _, path := range goSrc {
absDir, err := filepath.Abs(filepath.Dir(path))
if err != nil {
panic(err)
}
resultMap[absDir] = struct{}{}
}
results := []string{}
for k := range resultMap {
results = append(results, k)
}
return results
}()
goPkgs = func() []string {
results := []string{}
out, err := sh.Output("go", "list", "./...")
if err != nil {
panic(err)
}
for _, line := range strings.Split(out, "\n") {
if !strings.Contains(line, "/vendor/") {
results = append(results, line)
}
}
return results
}()
goCmds = func() []string {
results := []string{}
finfos, err := ioutil.ReadDir(cmdDir)
if err != nil {
panic(err)
}
for _, finfo := range finfos {
results = append(results, finfo.Name())
}
return results
}()
// Ensure output dirs exist
for _, dir := range outputDirs {
os.MkdirAll(dir, os.FileMode(0777))
}
}
func mustStr(r string, err error) string {
if err != nil {
panic(err)
}
return r
}
func getCoreTools() []string {
staticTools := []string{
"github.com/kardianos/govendor",
"github.com/wadey/gocovmerge",
"github.com/mattn/goveralls",
"github.com/tmthrgd/go-bindata/go-bindata",
"github.com/GoASTScanner/gas/cmd/gas", // workaround for Ast scanner
"github.com/alecthomas/gometalinter",
}
return staticTools
}
func getMetalinters() []string {
// Gometalinter should now be on the command line
dynamicTools := []string{}
goMetalinterHelp, _ := sh.Output("gometalinter", "--help")
linterRx := regexp.MustCompile(`\s+\w+:\s*\((.+)\)`)
for _, l := range strings.Split(goMetalinterHelp, "\n") {
linter := linterRx.FindStringSubmatch(l)
if len(linter) > 1 {
dynamicTools = append(dynamicTools, linter[1])
}
}
return dynamicTools
}
func ensureVendorSrcLink() error {
Log("Symlink vendor to tools dir")
if err := sh.Rm(toolsSrcDir); err != nil {
return err
}
if err := os.Symlink(toolsVendorDir, toolsSrcDir); err != nil {
return err
}
return nil
}
// concurrencyLimitedBuild executes a certain number of commands limited by concurrency
func concurrencyLimitedBuild(buildCmds ...interface{}) error {
resultsCh := make(chan error, len(buildCmds))
concurrencyControl := make(chan struct{}, concurrency)
for _, buildCmd := range buildCmds {
go func(buildCmd interface{}) {
concurrencyControl <- struct{}{}
resultsCh <- buildCmd.(func() error)()
<-concurrencyControl
}(buildCmd)
}
// Doesn't work at the moment
// mg.Deps(buildCmds...)
results := []error{}
var resultErr error = nil
for len(results) < len(buildCmds) {
err := <-resultsCh
results = append(results, err)
if err != nil {
fmt.Println(err)
resultErr = errors.New("parallel build failed")
}
fmt.Printf("Finished %v of %v\n", len(results), len(buildCmds))
}
return resultErr
}
// Tools builds build tools of the project and is depended on by all other build targets.
func Tools() (err error) {
// Catch panics and convert to errors
defer func() {
if perr := recover(); perr != nil {
err = perr.(error)
}
}()
if err := ensureVendorSrcLink(); err != nil {
return err
}
toolBuild := func(toolType string, tools ...string) error {
toolTargets := []interface{}{}
for _, toolImport := range tools {
toolParts := strings.Split(toolImport, "/")
toolBin := path.Join(toolsBinDir, toolParts[len(toolParts)-1])
Log("Check for changes:", toolBin, toolsVendorDir)
changed, terr := target.Dir(toolBin, toolsVendorDir)
if terr != nil {
if !os.IsNotExist(terr) {
panic(terr)
}
changed = true
}
if changed {
localToolImport := toolImport
f := func() error { return sh.RunWith(toolsEnv, "go", "install", "-v", localToolImport) }
toolTargets = append(toolTargets, f)
}
}
Log("Build", toolType, "tools")
if berr := concurrencyLimitedBuild(toolTargets...); berr != nil {
return berr
}
return nil
}
if berr := toolBuild("static", getCoreTools()...); berr != nil {
return berr
}
if berr := toolBuild("static", getMetalinters()...); berr != nil {
return berr
}
return nil
}
// UpdateTools automatically updates tool dependencies to the latest version.
func UpdateTools() error {
if err := ensureVendorSrcLink(); err != nil {
return err
}
// Ensure govendor is up to date without doing anything
govendorPkg := "github.com/kardianos/govendor"
govendorParts := strings.Split(govendorPkg, "/")
govendorBin := path.Join(toolsBinDir, govendorParts[len(govendorParts)-1])
sh.RunWith(toolsEnv, "go", "get", "-v", "-u", govendorPkg)
if changed, cerr := target.Dir(govendorBin, toolsSrcDir); changed || os.IsNotExist(cerr) {
if err := sh.RunWith(toolsEnv, "go", "install", "-v", govendorPkg); err != nil {
return err
}
} else if cerr != nil {
panic(cerr)
}
// Set current directory so govendor has the right path
previousPwd, wderr := os.Getwd()
if wderr != nil {
return wderr
}
if err := os.Chdir(toolDir); err != nil {
return err
}
// govendor fetch core tools
for _, toolImport := range append(getCoreTools(), getMetalinters()...) {
sh.RunV("govendor", "fetch", "-v", toolImport)
}
// change back to original working directory
if err := os.Chdir(previousPwd); err != nil {
return err
}
return nil
}
// Assets builds binary assets to be bundled into the binary.
func Assets() error {
mg.Deps(Tools)
if err := os.MkdirAll("assets/generated", os.FileMode(0777)); err != nil {
return err
}
return sh.RunV("go-bindata", "-pkg=assets", "-o", "assets/bindata.go", "-ignore=bindata.go",
"-ignore=.*.map$", "-prefix=assets/generated", "assets/generated/...")
}
// Lint runs gometalinter for code quality. CI will run this before accepting PRs.
func Lint() error {
mg.Deps(Tools)
args := []string{"-j", fmt.Sprintf("%v", concurrency), fmt.Sprintf("--deadline=%s",
linterDeadline.String()), "--enable-all", "--line-length=120",
"--disable=gocyclo", "--disable=testify", "--disable=test", "--disable=lll", "--exclude=assets/bindata.go"}
return sh.RunV("gometalinter", append(args, goDirs...)...)
}
// Style checks formatting of the file. CI will run this before acceptiing PRs.
func Style() error {
mg.Deps(Tools)
args := []string{"--disable-all", "--enable=gofmt", "--enable=goimports"}
return sh.RunV("gometalinter", append(args, goSrc...)...)
}
// Fmt automatically formats all source code files
func Fmt() error {
mg.Deps(Tools)
fmtErr := sh.RunV("gofmt", append([]string{"-s", "-w"}, goSrc...)...)
if fmtErr != nil {
return fmtErr
}
impErr := sh.RunV("goimports", append([]string{"-w"}, goSrc...)...)
if impErr != nil {
return fmtErr
}
return nil
}
func listCoverageFiles() ([]string, error) {
result := []string{}
finfos, derr := ioutil.ReadDir(coverageDir)
if derr != nil {
return result, derr
}
for _, finfo := range finfos {
result = append(result, path.Join(coverageDir, finfo.Name()))
}
return result, nil
}
// Test run test suite
func Test() error {
mg.Deps(Tools)
// Ensure coverage directory exists
if err := os.MkdirAll(coverageDir, os.FileMode(0777)); err != nil {
return err
}
// Clean up coverage directory
coverFiles, derr := listCoverageFiles()
if derr != nil {
return derr
}
for _, coverFile := range coverFiles {
if err := sh.Rm(coverFile); err != nil {
return err
}
}
// Run tests
coverProfiles := []string{}
for _, pkg := range goPkgs {
coverProfile := path.Join(coverageDir, fmt.Sprintf("%s%s", strings.Replace(pkg, "/", "-", -1), ".out"))
testErr := sh.Run("go", "test", "-v", "-covermode", "count", fmt.Sprintf("-coverprofile=%s", coverProfile),
pkg)
if testErr != nil {
return testErr
}
coverProfiles = append(coverProfiles, coverProfile)
}
return nil
}
// Build the intgration test binary
func IntegrationTestBinary() error {
changed, err := target.Path("postgres_exporter_integration_test", goSrc...)
if (changed && (err == nil)) || os.IsNotExist(err) {
return sh.RunWith(map[string]string{"CGO_ENABLED": "0"}, "go", "test", "./cmd/postgres_exporter",
"-c", "-tags", "integration",
"-a", "-ldflags", "-extldflags '-static'",
"-X", fmt.Sprintf("main.Branch=%s", branch),
"-X", fmt.Sprintf("main.BuildDate=%s", buildDate),
"-X", fmt.Sprintf("main.Revision=%s", revision),
"-X", fmt.Sprintf("main.VersionShort=%s", versionShort),
"-o", "postgres_exporter_integration_test", "-cover", "-covermode", "count")
}
return err
}
// TestIntegration runs integration tests
func TestIntegration() error {
mg.Deps(Binary, IntegrationTestBinary)
exporterPath := mustStr(filepath.Abs("postgres_exporter"))
testBinaryPath := mustStr(filepath.Abs("postgres_exporter_integration_test"))
testScriptPath := mustStr(filepath.Abs("postgres_exporter_integration_test_script"))
integrationCoverageProfile := path.Join(coverageDir, "cover.integration.out")
return sh.RunV("cmd/postgres_exporter/tests/test-smoke", exporterPath,
fmt.Sprintf("%s %s %s", testScriptPath, testBinaryPath, integrationCoverageProfile))
}
// Coverage sums up the coverage profiles in .coverage. It does not clean up after itself or before.
func Coverage() error {
// Clean up coverage directory
coverFiles, derr := listCoverageFiles()
if derr != nil {
return derr
}
mergedCoverage, err := sh.Output("gocovmerge", coverFiles...)
if err != nil {
return err
}
return ioutil.WriteFile(constCoverFile, []byte(mergedCoverage), os.FileMode(0777))
}
// All runs a full suite suitable for CI
func All() error {
mg.SerialDeps(Style, Lint, Test, TestIntegration, Coverage, Release)
return nil
}
// Release builds release archives under the release/ directory
func Release() error {
mg.Deps(ReleaseBin)
for _, platform := range platforms {
owd, wderr := os.Getwd()
if wderr != nil {
return wderr
}
os.Chdir(binDir)
if platform.OS == "windows" {
// build a zip binary as well
err := archiver.Zip.Make(fmt.Sprintf("%s.zip", platform.ReleaseBase()), []string{platform.ArchiveDir()})
if err != nil {
return err
}
}
// build tar gz
err := archiver.TarGz.Make(fmt.Sprintf("%s.tar.gz", platform.ReleaseBase()), []string{platform.ArchiveDir()})
if err != nil {
return err
}
os.Chdir(owd)
}
return nil
}
func makeBuilder(cmd string, platform Platform) func() error {
f := func() error {
// Depend on assets
mg.Deps(Assets)
cmdSrc := fmt.Sprintf("./%s/%s", mustStr(filepath.Rel(curDir, cmdDir)), cmd)
Log("Make platform binary directory:", platform.PlatformDir())
if err := os.MkdirAll(platform.PlatformDir(), os.FileMode(0777)); err != nil {
return err
}
Log("Checking for changes:", platform.PlatformBin(cmd))
if changed, err := target.Path(platform.PlatformBin(cmd), goSrc...); !changed {
if err != nil {
if !os.IsNotExist(err) {
return err
}
} else {
return nil
}
}
fmt.Println("Building", platform.PlatformBin(cmd))
return sh.RunWith(map[string]string{"CGO_ENABLED": "0", "GOOS": platform.OS, "GOARCH": platform.Arch},
"go", "build", "-a", "-ldflags", fmt.Sprintf("-extldflags '-static' -X main.Version=%s", version),
"-o", platform.PlatformBin(cmd), cmdSrc)
}
return f
}
func getCurrentPlatform() *Platform {
var curPlatform *Platform
for _, p := range platforms {
if p.OS == runtime.GOOS && p.Arch == runtime.GOARCH {
storedP := p
curPlatform = &storedP
}
}
Log("Determined current platform:", curPlatform)
return curPlatform
}
// Binary build a binary for the current platform
func Binary() error {
curPlatform := getCurrentPlatform()
if curPlatform == nil {
return errors.New("current platform is not supported")
}
for _, cmd := range goCmds {
err := makeBuilder(cmd, *curPlatform)()
if err != nil {
return err
}
// Make a root symlink to the build
cmdPath := path.Join(curDir, cmd)
os.Remove(cmdPath)
if err := os.Symlink(curPlatform.PlatformBin(cmd), cmdPath); err != nil {
return err
}
}
return nil
}
// ReleaseBin builds cross-platform release binaries under the bin/ directory
func ReleaseBin() error {
buildCmds := []interface{}{}
for _, cmd := range goCmds {
for _, platform := range platforms {
buildCmds = append(buildCmds, makeBuilder(cmd, platform))
}
}
resultsCh := make(chan error, len(buildCmds))
concurrencyControl := make(chan struct{}, concurrency)
for _, buildCmd := range buildCmds {
go func(buildCmd interface{}) {
concurrencyControl <- struct{}{}
resultsCh <- buildCmd.(func() error)()
<-concurrencyControl
}(buildCmd)
}
// Doesn't work at the moment
// mg.Deps(buildCmds...)
results := []error{}
var resultErr error = nil
for len(results) < len(buildCmds) {
err := <-resultsCh
results = append(results, err)
if err != nil {
fmt.Println(err)
resultErr = errors.New("parallel build failed")
}
fmt.Printf("Finished %v of %v\n", len(results), len(buildCmds))
}
return resultErr
}
// Docker builds the docker image
func Docker() error {
mg.Deps(Binary)
p := getCurrentPlatform()
if p == nil {
return errors.New("current platform is not supported")
}
return sh.RunV("docker", "build",
fmt.Sprintf("--build-arg=binary=%s",
mustStr(filepath.Rel(curDir, p.PlatformBin("postgres_exporter")))),
"-t", containerName, ".")
}
// Clean deletes build output and cleans up the working directory
func Clean() error {
for _, name := range goCmds {
if err := sh.Rm(path.Join(binDir, name)); err != nil {
return err
}
}
for _, name := range outputDirs {
if err := sh.Rm(name); err != nil {
return err
}
}
return nil
}
// Debug prints the value of internal state variables
func Debug() error {
fmt.Println("Source Files:", goSrc)
fmt.Println("Packages:", goPkgs)
fmt.Println("Directories:", goDirs)
fmt.Println("Command Paths:", goCmds)
fmt.Println("Output Dirs:", outputDirs)
fmt.Println("Tool Src Dir:", toolsSrcDir)
fmt.Println("Tool Vendor Dir:", toolsVendorDir)
fmt.Println("Tool GOPATH:", toolsGoPath)
fmt.Println("PATH:", os.Getenv("PATH"))
return nil
}
// Autogen configure local git repository with commit hooks
func Autogen() error {
fmt.Println("Installing git hooks in local repository...")
return os.Link(path.Join(curDir, toolDir, "pre-commit"), ".git/hooks/pre-commit")
}

View File

4
tools/.gitignore vendored
View File

@ -1,4 +0,0 @@
/pkg
/bin
/tools.deps
/metatools.deps

View File

@ -1,9 +0,0 @@
Vendored versions of the build tooling.
gocovmerge is used to merge coverage reports for uploading to a service like
coveralls, and gometalinter conveniently incorporates multiple Go linters.
By vendoring both, we gain a self-contained build system.
Run `make all` to build, and `make update` to update.

View File

@ -1,7 +0,0 @@
- [Larz Conwell](https://github.com/larzconwell)
- [Steve Kaliski](https://github.com/sjkaliski)
- [NHOrus](https://github.com/NHOrus)
- [Attila Fülöp](https://github.com/AttilaFueloep)
- [Gereon Frey](https://github.com/gfrey)
- [Aaron Bieber](https://github.com/qbit)
- [Ricky Medina](https://github.com/r-medina)

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013-2015 Bowery, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,38 +0,0 @@
# Prompt
[![Circle CI](https://circleci.com/gh/Bowery/prompt/tree/master.png?style=badge)](https://circleci.com/gh/Bowery/prompt/tree/master)
[![GoDoc](https://godoc.org/github.com/Bowery/prompt?status.png)](https://godoc.org/github.com/Bowery/prompt)
Prompt is a cross platform line-editing prompting library. Read the GoDoc page
for more info and for API details.
## Features
- Keyboard shortcuts in prompts
- History support
- Secure password prompt
- Custom prompt support
- Fallback prompt for unsupported terminals
- ANSI conversion for Windows
## Todo
- Multi-line prompt as a Terminal option
- Make refresh less jittery on Windows([possible reason](https://github.com/Bowery/prompt/blob/master/output_windows.go#L108))
- Multi-byte character support on Windows
- `AnsiWriter` should execute the equivalent ANSI escape code functionality on Windows
- Support for more ANSI escape codes on Windows.
- More keyboard shortcuts from Readlines shortcut list
## Contributing
Make sure Go is setup and running the latest release version, and make sure your `GOPATH` is setup properly.
Follow the guidelines [here](https://guides.github.com/activities/contributing-to-open-source/#contributing).
Please be sure to `gofmt` any code before doing commits. You can simply run `gofmt -w .` to format all the code in the directory.
Lastly don't forget to add your name to [`CONTRIBUTORS.md`](https://github.com/Bowery/prompt/blob/master/CONTRIBUTORS.md)
## License
Prompt is MIT licensed, details can be found [here](https://raw.githubusercontent.com/Bowery/prompt/master/LICENSE).

View File

@ -1,39 +0,0 @@
// +build linux darwin freebsd openbsd netbsd dragonfly solaris
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"os"
)
// AnsiReader is an io.Reader that wraps an *os.File.
type AnsiReader struct {
file *os.File
}
// NewAnsiReader creates a AnsiReader from the given input file.
func NewAnsiReader(in *os.File) *AnsiReader {
return &AnsiReader{file: in}
}
// Read reads data from the input file into b.
func (ar *AnsiReader) Read(b []byte) (int, error) {
return ar.file.Read(b)
}
// AnsiWriter is an io.Writer that wraps an *os.File.
type AnsiWriter struct {
file *os.File
}
// NewAnsiWriter creates a AnsiWriter from the given output file.
func NewAnsiWriter(out *os.File) *AnsiWriter {
return &AnsiWriter{file: out}
}
// Write writes data from b into the input file.
func (aw *AnsiWriter) Write(b []byte) (int, error) {
return aw.file.Write(b)
}

View File

@ -1,510 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"bytes"
"os"
"unicode/utf8"
"unsafe"
)
// keyEventType is the key event type for an input record.
const keyEventType = 0x0001
var (
readConsoleInput = kernel.NewProc("ReadConsoleInputW")
)
// inputRecord describes a input event from a console.
type inputRecord struct {
eventType uint16
// Magic to get around the union C type, cast
// event to the type using unsafe.Pointer.
_ [2]byte
event [16]byte
}
// keyEventRecord describes a keyboard event.
type keyEventRecord struct {
keyDown int32
repeatCount uint16
virtualKeyCode uint16
virtualScanCode uint16
char uint16
controlKeyState uint32
}
// AnsiReader is an io.Reader that reads from a given file and converts Windows
// key codes to their equivalent ANSI escape codes.
type AnsiReader struct {
fd uintptr
buf []rune
}
// NewAnsiReader creates a AnsiReader from the given input file.
func NewAnsiReader(in *os.File) *AnsiReader {
return &AnsiReader{fd: in.Fd()}
}
// Read reads data from the input converting to ANSI escape codes that can be
// read over multiple Reads.
func (ar *AnsiReader) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
if len(ar.buf) == 0 {
var runes []rune
var read uint32
rec := new(inputRecord)
for runes == nil {
ret, _, err := readConsoleInput.Call(ar.fd, uintptr(unsafe.Pointer(rec)),
1, uintptr(unsafe.Pointer(&read)))
if ret == 0 {
return 0, err
}
if rec.eventType != keyEventType {
continue
}
ke := (*keyEventRecord)(unsafe.Pointer(&rec.event))
if ke.keyDown == 0 {
continue
}
shift := false
if ke.controlKeyState&shiftKey != 0 {
shift = true
}
ctrl := false
if ke.controlKeyState&leftCtrlKey != 0 || ke.controlKeyState&rightCtrlKey != 0 {
ctrl = true
}
alt := false
if ke.controlKeyState&leftAltKey != 0 || ke.controlKeyState&rightAltKey != 0 {
alt = true
}
// Backspace, Return, Space.
if ke.char == ctrlH || ke.char == returnKey || ke.char == spaceKey {
code := string(returnKey)
if ke.char == ctrlH {
code = string(backKey)
} else if ke.char == spaceKey {
code = string(spaceKey)
}
if alt {
code = string(escKey) + code
}
runes = []rune(code)
break
}
// Generate runes for the chars and key codes.
if ke.char > 0 {
runes = []rune{rune(ke.char)}
} else {
code := string(escKey)
switch ke.virtualKeyCode {
case f1Key:
if ctrl {
continue
}
code += ar.shortFunction("P", shift, ctrl, alt)
case f2Key:
code += ar.shortFunction("Q", shift, ctrl, alt)
case f3Key:
code += ar.shortFunction("R", shift, ctrl, alt)
case f4Key:
code += ar.shortFunction("S", shift, ctrl, alt)
case f5Key:
code += ar.longFunction("15", shift, ctrl, alt)
case f6Key:
code += ar.longFunction("17", shift, ctrl, alt)
case f7Key:
code += ar.longFunction("18", shift, ctrl, alt)
case f8Key:
code += ar.longFunction("19", shift, ctrl, alt)
case f9Key:
code += ar.longFunction("20", shift, ctrl, alt)
case f10Key:
code += ar.longFunction("21", shift, ctrl, alt)
case f11Key:
code += ar.longFunction("23", shift, ctrl, alt)
case f12Key:
code += ar.longFunction("24", shift, ctrl, alt)
case insertKey:
if shift || ctrl {
continue
}
code += ar.longFunction("2", shift, ctrl, alt)
case deleteKey:
code += ar.longFunction("3", shift, ctrl, alt)
case homeKey:
code += "OH"
case endKey:
code += "OF"
case pgupKey:
if shift {
continue
}
code += ar.longFunction("5", shift, ctrl, alt)
case pgdownKey:
if shift {
continue
}
code += ar.longFunction("6", shift, ctrl, alt)
case upKey:
code += ar.arrow("A", shift, ctrl, alt)
case downKey:
code += ar.arrow("B", shift, ctrl, alt)
case leftKey:
code += ar.arrow("D", shift, ctrl, alt)
case rightKey:
code += ar.arrow("C", shift, ctrl, alt)
default:
continue
}
runes = []rune(code)
}
}
ar.buf = runes
}
// Get items from the buffer.
var n int
for i, r := range ar.buf {
if utf8.RuneLen(r) > len(b) {
ar.buf = ar.buf[i:]
return n, nil
}
nr := utf8.EncodeRune(b, r)
b = b[nr:]
n += nr
}
ar.buf = nil
return n, nil
}
// shortFunction creates a short function code.
func (ar *AnsiReader) shortFunction(ident string, shift, ctrl, alt bool) string {
code := "O"
if shift {
code += "1;2"
} else if ctrl {
code += "1;5"
} else if alt {
code += "1;3"
}
return code + ident
}
// longFunction creates a long function code.
func (ar *AnsiReader) longFunction(ident string, shift, ctrl, alt bool) string {
code := "["
code += ident
if shift {
code += ";2"
} else if ctrl {
code += ";5"
} else if alt {
code += ";3"
}
return code + "~"
}
// arrow creates an arrow code.
func (ar *AnsiReader) arrow(ident string, shift, ctrl, alt bool) string {
code := "["
if shift {
code += "1;2"
} else if ctrl {
code += "1;5"
} else if alt {
code += "1;3"
}
return code + ident
}
// AnsiWriter is an io.Writer that writes to a given file and converts ANSI
// escape codes to their equivalent Windows functionality.
type AnsiWriter struct {
file *os.File
buf []byte
}
// NewAnsiWriter creates a AnsiWriter from the given output.
func NewAnsiWriter(out *os.File) *AnsiWriter {
return &AnsiWriter{file: out}
}
// Write writes the buffer filtering out ANSI escape codes and converting to
// the Windows functionality needed. ANSI escape codes may be found over multiple
// Writes.
func (aw *AnsiWriter) Write(b []byte) (int, error) {
needsProcessing := bytes.Contains(b, []byte(string(escKey)))
if len(aw.buf) > 0 {
needsProcessing = true
}
if !needsProcessing {
return aw.file.Write(b)
}
var p []byte
for _, char := range b {
// Found the beginning of an escape.
if char == escKey {
aw.buf = append(aw.buf, char)
continue
}
// Funtion identifiers.
if len(aw.buf) == 1 && (char == '_' || char == 'P' || char == '[' ||
char == ']' || char == '^' || char == ' ' || char == '#' ||
char == '%' || char == '(' || char == ')' || char == '*' ||
char == '+') {
aw.buf = append(aw.buf, char)
continue
}
// Cursor functions.
if len(aw.buf) == 1 && (char == '7' || char == '8') {
// Add another char before because finish skips 2 items.
aw.buf = append(aw.buf, '_', char)
err := aw.finish(nil)
if err != nil {
return 0, err
}
continue
}
// Keyboard functions.
if len(aw.buf) == 1 && (char == '=' || char == '>') {
aw.buf = append(aw.buf, char)
err := aw.finish(nil)
if err != nil {
return 0, err
}
continue
}
// Bottom left function.
if len(aw.buf) == 1 && char == 'F' {
// Add extra char for finish.
aw.buf = append(aw.buf, '_', char)
err := aw.finish(nil)
if err != nil {
return 0, err
}
continue
}
// Reset function.
if len(aw.buf) == 1 && char == 'c' {
// Add extra char for finish.
aw.buf = append(aw.buf, '_', char)
err := aw.finish(nil)
if err != nil {
return 0, err
}
continue
}
// Space functions.
if len(aw.buf) >= 2 && aw.buf[1] == ' ' && (char == 'F' || char == 'G' ||
char == 'L' || char == 'M' || char == 'N') {
aw.buf = append(aw.buf, char)
err := aw.finish(nil)
if err != nil {
return 0, err
}
continue
}
// Number functions.
if len(aw.buf) >= 2 && aw.buf[1] == '#' && (char >= '3' && char <= '6') ||
char == '8' {
aw.buf = append(aw.buf, char)
err := aw.finish(nil)
if err != nil {
return 0, err
}
continue
}
// Percentage functions.
if len(aw.buf) >= 2 && aw.buf[1] == '%' && (char == '@' || char == 'G') {
aw.buf = append(aw.buf, char)
err := aw.finish(nil)
if err != nil {
return 0, err
}
continue
}
// Character set functions.
if len(aw.buf) >= 2 && (aw.buf[1] == '(' || aw.buf[1] == ')' ||
aw.buf[1] == '*' || aw.buf[1] == '+') && (char == '0' ||
(char >= '4' && char <= '7') || char == '=' || (char >= 'A' &&
char <= 'C') || char == 'E' || char == 'H' || char == 'K' ||
char == 'Q' || char == 'R' || char == 'Y') {
aw.buf = append(aw.buf, char)
err := aw.finish(nil)
if err != nil {
return 0, err
}
continue
}
// APC functions.
if len(aw.buf) >= 2 && aw.buf[1] == '_' {
aw.buf = append(aw.buf, char)
// End of APC.
if char == '\\' && aw.buf[len(aw.buf)-1] == escKey {
err := aw.finish(nil)
if err != nil {
return 0, err
}
}
continue
}
// DC functions.
if len(aw.buf) >= 2 && aw.buf[1] == 'P' {
aw.buf = append(aw.buf, char)
// End of DC.
if char == '\\' && aw.buf[len(aw.buf)-1] == escKey {
err := aw.finish(nil)
if err != nil {
return 0, err
}
}
continue
}
// CSI functions.
if len(aw.buf) >= 2 && aw.buf[1] == '[' {
aw.buf = append(aw.buf, char)
// End of CSI.
if char == '@' || (char >= 'A' && char <= 'M') || char == 'P' ||
char == 'S' || char == 'T' || char == 'X' || char == 'Z' ||
char == '`' || (char >= 'b' && char <= 'd') || (char >= 'f' &&
char <= 'i') || (char >= 'l' && char <= 'n') || (char >= 'p' &&
char <= 't') || char == 'w' || char == 'x' || char == 'z' ||
char == '{' || char == '|' {
err := aw.finish(nil)
if err != nil {
return 0, err
}
}
continue
}
// OSC functions.
if len(aw.buf) >= 2 && aw.buf[1] == ']' {
aw.buf = append(aw.buf, char)
// Capture incomplete code.
if len(aw.buf) == 4 && aw.buf[2] == '0' && char == ';' {
err := aw.finish(nil)
if err != nil {
return 0, err
}
continue
}
// End of OSC.
if (char == '\\' && aw.buf[len(aw.buf)-1] == escKey) || char == ctrlG {
err := aw.finish(nil)
if err != nil {
return 0, err
}
}
continue
}
// PM functions.
if len(aw.buf) >= 2 && aw.buf[1] == '^' {
aw.buf = append(aw.buf, char)
// End of PM.
if char == '\\' && aw.buf[len(aw.buf)-1] == escKey {
err := aw.finish(nil)
if err != nil {
return 0, err
}
}
continue
}
// Normal character, resets escape buffer.
if len(aw.buf) > 0 {
aw.buf = nil
}
p = append(p, char)
}
_, err := aw.file.Write(p)
return len(b), err
}
// finish finishes an ANSI escape code and calls the parsing function. Afterwards
// the escape buffer is emptied.
func (aw *AnsiWriter) finish(parse func([]byte) error) error {
var err error
if parse != nil {
err = parse(aw.buf[2:])
}
aw.buf = nil
return err
}

View File

@ -1,152 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"os"
"unicode/utf8"
)
// Buffer contains state for line editing and writing.
type Buffer struct {
Out *os.File
Prompt string
Echo bool
Cols int
pos int
size int
data []rune
}
// NewBuffer creates a buffer writing to out if echo is true.
func NewBuffer(prompt string, out *os.File, echo bool) *Buffer {
return &Buffer{
Out: out,
Prompt: prompt,
Echo: echo,
}
}
// String returns the data as a string.
func (buf *Buffer) String() string {
return string(buf.data[:buf.size])
}
// Insert inserts characters at the cursors position.
func (buf *Buffer) Insert(rs ...rune) error {
rsLen := len(rs)
total := buf.size + rsLen
if total > len(buf.data) {
buf.data = append(buf.data, make([]rune, rsLen)...)
}
// Shift characters to make room in the correct pos.
if buf.size != buf.pos {
copy(buf.data[buf.pos+rsLen:buf.size+rsLen], buf.data[buf.pos:buf.size])
}
for _, r := range rs {
buf.data[buf.pos] = r
buf.pos++
buf.size++
}
return buf.Refresh()
}
// Set sets the content in the buffer.
func (buf *Buffer) Set(rs ...rune) error {
rsLen := len(rs)
buf.data = rs
buf.pos = rsLen
buf.size = rsLen
return buf.Refresh()
}
// Start moves the cursor to the start.
func (buf *Buffer) Start() error {
if buf.pos <= 0 {
return nil
}
buf.pos = 0
return buf.Refresh()
}
// End moves the cursor to the end.
func (buf *Buffer) End() error {
if buf.pos >= buf.size {
return nil
}
buf.pos = buf.size
return buf.Refresh()
}
// Left moves the cursor one character left.
func (buf *Buffer) Left() error {
if buf.pos <= 0 {
return nil
}
buf.pos--
return buf.Refresh()
}
// Right moves the cursor one character right.
func (buf *Buffer) Right() error {
if buf.pos >= buf.size {
return nil
}
buf.pos++
return buf.Refresh()
}
// Del removes the character at the cursor position.
func (buf *Buffer) Del() error {
if buf.pos >= buf.size {
return nil
}
// Shift characters after position back one.
copy(buf.data[buf.pos:], buf.data[buf.pos+1:buf.size])
buf.size--
return buf.Refresh()
}
// DelLeft removes the character to the left.
func (buf *Buffer) DelLeft() error {
if buf.pos <= 0 {
return nil
}
// Shift characters from position back one.
copy(buf.data[buf.pos-1:], buf.data[buf.pos:buf.size])
buf.pos--
buf.size--
return buf.Refresh()
}
// EndLine ends the line with CRLF.
func (buf *Buffer) EndLine() error {
_, err := buf.Out.Write(crlf)
return err
}
// toBytes converts a slice of runes to its equivalent in bytes.
func toBytes(runes []rune) []byte {
var bytes []byte
char := make([]byte, utf8.UTFMax)
for _, r := range runes {
n := utf8.EncodeRune(char, r)
bytes = append(bytes, char[:n]...)
}
return bytes
}

View File

@ -1,76 +0,0 @@
// +build linux darwin freebsd openbsd netbsd dragonfly solaris
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"fmt"
)
// Refresh rewrites the prompt and buffer.
func (buf *Buffer) Refresh() error {
// If we're not echoing just write prompt.
if !buf.Echo {
_, err := buf.Out.Write(mvLeftEdge)
if err != nil {
return err
}
_, err = buf.Out.Write([]byte(buf.Prompt))
if err != nil {
return err
}
_, err = buf.Out.Write(delRight)
return err
}
prLen := len(buf.Prompt)
start := 0
size := buf.size
pos := buf.pos
// Get slice range that should be visible.
for prLen+pos >= buf.Cols {
start++
size--
pos--
}
for prLen+size > buf.Cols {
size--
}
_, err := buf.Out.Write(mvLeftEdge)
if err != nil {
return err
}
_, err = buf.Out.Write([]byte(buf.Prompt))
if err != nil {
return err
}
_, err = buf.Out.Write(toBytes(buf.data[start : size+start]))
if err != nil {
return err
}
_, err = buf.Out.Write(delRight)
if err != nil {
return err
}
_, err = buf.Out.Write([]byte(fmt.Sprintf(mvToCol, pos+prLen)))
return err
}
// ClsScreen clears the screen and refreshes.
func (buf *Buffer) ClsScreen() error {
_, err := buf.Out.Write(clsScreen)
if err != nil {
return err
}
return buf.Refresh()
}

View File

@ -1,150 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"unsafe"
)
var (
fillConsoleOutputCharacter = kernel.NewProc("FillConsoleOutputCharacterW")
setConsoleCursorPosition = kernel.NewProc("SetConsoleCursorPosition")
)
// Refresh rewrites the prompt and buffer.
func (buf *Buffer) Refresh() error {
csbi := new(consoleScreenBufferInfo)
ret, _, err := getConsoleScreenBufferInfo.Call(buf.Out.Fd(),
uintptr(unsafe.Pointer(csbi)))
if ret == 0 {
return err
}
// If we're not echoing just write prompt.
if !buf.Echo {
err = buf.delLine(csbi)
if err != nil {
return err
}
err = buf.mvLeftEdge(csbi)
if err != nil {
return err
}
_, err = buf.Out.Write([]byte(buf.Prompt))
return err
}
prLen := len(buf.Prompt)
start := 0
size := buf.size
pos := buf.pos
// Get slice range that should be visible.
for prLen+pos >= buf.Cols {
start++
size--
pos--
}
for prLen+size > buf.Cols {
size--
}
err = buf.delLine(csbi)
if err != nil {
return err
}
err = buf.mvLeftEdge(csbi)
if err != nil {
return err
}
_, err = buf.Out.Write([]byte(buf.Prompt))
if err != nil {
return err
}
_, err = buf.Out.Write(toBytes(buf.data[start : size+start]))
if err != nil {
return err
}
return buf.mvToCol(csbi, pos+prLen)
}
// ClsScreen clears the screen and refreshes.
func (buf *Buffer) ClsScreen() error {
var written uint32
coords := new(coord)
csbi := new(consoleScreenBufferInfo)
ret, _, err := getConsoleScreenBufferInfo.Call(buf.Out.Fd(),
uintptr(unsafe.Pointer(csbi)))
if ret == 0 {
return err
}
// Clear everything from 0,0.
ret, _, err = fillConsoleOutputCharacter.Call(buf.Out.Fd(), uintptr(' '),
uintptr(csbi.size.x*csbi.size.y), uintptr(*(*int32)(unsafe.Pointer(coords))),
uintptr(unsafe.Pointer(&written)))
if ret == 0 {
return err
}
// Set cursor at 0,0.
ret, _, err = setConsoleCursorPosition.Call(buf.Out.Fd(),
uintptr(*(*int32)(unsafe.Pointer(coords))))
if ret == 0 {
return err
}
return buf.Refresh()
}
// delLine deletes the line the csbi cursor is positioned on.
// TODO: Possible refresh jittering reason, instead we should copy the Unix
// code and write over contents and then remove everything to the right.
func (buf *Buffer) delLine(csbi *consoleScreenBufferInfo) error {
var written uint32
coords := &coord{y: csbi.cursorPosition.y}
ret, _, err := fillConsoleOutputCharacter.Call(buf.Out.Fd(), uintptr(' '),
uintptr(csbi.size.x), uintptr(*(*int32)(unsafe.Pointer(coords))),
uintptr(unsafe.Pointer(&written)))
if ret == 0 {
return err
}
return nil
}
// mvLeftEdge moves the cursor to the beginning of the line the csbi cursor
// is positioned on.
func (buf *Buffer) mvLeftEdge(csbi *consoleScreenBufferInfo) error {
coords := &coord{y: csbi.cursorPosition.y}
ret, _, err := setConsoleCursorPosition.Call(buf.Out.Fd(),
uintptr(*(*int32)(unsafe.Pointer(coords))))
if ret == 0 {
return err
}
return nil
}
// mvTolCol moves the cursor to the col on the line the csbi cursor is
// positioned on.
func (buf *Buffer) mvToCol(csbi *consoleScreenBufferInfo, x int) error {
coords := &coord{x: int16(x), y: csbi.cursorPosition.y}
ret, _, err := setConsoleCursorPosition.Call(buf.Out.Fd(),
uintptr(*(*int32)(unsafe.Pointer(coords))))
if ret == 0 {
return err
}
return nil
}

View File

@ -1,15 +0,0 @@
// +build darwin freebsd openbsd netbsd dragonfly
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"golang.org/x/sys/unix"
)
const (
tcgets = unix.TIOCGETA
tcsets = unix.TIOCSETA
tcsetsf = unix.TIOCSETAF
)

View File

@ -1,13 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"golang.org/x/sys/unix"
)
const (
tcgets = unix.TCGETS
tcsets = unix.TCSETS
tcsetsf = unix.TCSETSF
)

View File

@ -1,41 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"os"
"golang.org/x/sys/unix"
)
const (
tcgets = unix.TCGETS
tcsetsf = unix.TCSETSF
tcsets = unix.TCSETS
)
// terminalSize retrieves the cols/rows for the terminal connected to out.
func terminalSize(out *os.File) (int, int, error) {
ws, err := unix.IoctlGetWinsize(int(out.Fd()), unix.TIOCGWINSZ)
if err != nil {
return 0, 0, err
}
return int(ws.Col), int(ws.Row), nil
}
// getTermios retrieves the termios settings for the terminal descriptor.
func getTermios(fd uintptr) (*unix.Termios, error) {
return unix.IoctlGetTermios(int(fd), tcgets)
}
// setTermios sets the termios settings for the terminal descriptor,
// optionally flushing the buffer before setting.
func setTermios(fd uintptr, flush bool, mode *unix.Termios) error {
req := tcsets
if flush {
req = tcsetsf
}
return unix.IoctlSetTermios(int(fd), req, mode)
}

View File

@ -1,62 +0,0 @@
// +build linux darwin freebsd openbsd netbsd dragonfly
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"os"
"unsafe"
"golang.org/x/sys/unix"
)
// winsize contains the size for the terminal.
type winsize struct {
rows uint16
cols uint16
_ uint32
}
// terminalSize retrieves the cols/rows for the terminal connected to out.
func terminalSize(out *os.File) (int, int, error) {
ws := new(winsize)
_, _, err := unix.Syscall(unix.SYS_IOCTL, out.Fd(),
uintptr(unix.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
if err != 0 {
return 0, 0, err
}
return int(ws.cols), int(ws.rows), nil
}
// getTermios retrieves the termios settings for the terminal descriptor.
func getTermios(fd uintptr) (*unix.Termios, error) {
termios := new(unix.Termios)
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, tcgets,
uintptr(unsafe.Pointer(termios)))
if err != 0 {
return nil, err
}
return termios, nil
}
// setTermios sets the termios settings for the terminal descriptor,
// optionally flushing the buffer before setting.
func setTermios(fd uintptr, flush bool, mode *unix.Termios) error {
req := tcsets
if flush {
req = tcsetsf
}
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(req),
uintptr(unsafe.Pointer(mode)))
if err != 0 {
return err
}
return nil
}

View File

@ -1,41 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
package prompt
// Line ending in raw mode.
var crlf = []byte("\r\n")
const (
backKey = '\u007f'
escKey = '\u001B'
spaceKey = '\u0020'
)
const (
ctrlA = iota + 1
ctrlB
ctrlC
ctrlD
ctrlE
ctrlF
ctrlG
ctrlH
tabKey
ctrlJ
ctrlK
ctrlL
returnKey
ctrlN
ctrlO
ctrlP
ctrlQ
ctrlR
ctrlS
ctrlT
ctrlU
ctrlV
ctrlW
ctrlX
ctrlY
ctrlZ
)

View File

@ -1,13 +0,0 @@
// +build linux darwin freebsd openbsd netbsd dragonfly solaris
// Copyright 2013-2015 Bowery, Inc.
package prompt
const mvToCol = "\u001b[0G\u001b[%dC"
var (
mvLeftEdge = []byte("\u001b[0G")
clsScreen = []byte("\u001b[H\u001b[2J")
delRight = []byte("\u001b[0K")
)

View File

@ -1,34 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
package prompt
const (
f1Key = 0x70 + iota
f2Key
f3Key
f4Key
f5Key
f6Key
f7Key
f8Key
f9Key
f10Key
f11Key
f12Key
homeKey = 0x24
endKey = 0x23
upKey = 0x26
downKey = 0x28
rightKey = 0x27
leftKey = 0x25
insertKey = 0x2d
pgupKey = 0x21
pgdownKey = 0x22
deleteKey = 0x2e
leftAltKey = 0x2
rightAltKey = 0x1
leftCtrlKey = 0x8
rightCtrlKey = 0x4
shiftKey = 0x10
)

View File

@ -1,85 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
// Package prompt implements a cross platform line-editing prompt. It also
// provides routines to use ANSI escape sequences across platforms for
// terminal connected io.Readers/io.Writers.
//
// If os.Stdin isn't connected to a terminal or (on Unix)if the terminal
// doesn't support the ANSI escape sequences needed a fallback prompt is
// provided that doesn't do line-editing. Unix terminals that are not supported
// will have the TERM environment variable set to either "dumb" or "cons25".
//
// The keyboard shortcuts are similar to those found in the Readline library:
//
// - Enter / CTRL+D
// - End the line.
// - CTRL+C
// - End the line, return error `ErrCTRLC`.
// - Backspace
// - Remove the character to the left.
// - CTRL+L
// - Clear the screen(keeping the current lines content).
// - Home / End
// - Jump to the beginning/end of the line.
// - Up arrow / Down arrow
// - Go back and forward in the history.
// - Left arrow / Right arrow
// - Move left/right one character.
// - Delete
// - Remove the character to the right.
package prompt
// Basic is a wrapper around Terminal.Basic.
func Basic(prefix string, required bool) (string, error) {
term, err := NewTerminal()
if err != nil {
return "", err
}
defer term.Close()
return term.Basic(prefix, required)
}
// BasicDefault is a wrapper around Terminal.BasicDefault.
func BasicDefault(prefix, def string) (string, error) {
term, err := NewTerminal()
if err != nil {
return "", err
}
defer term.Close()
return term.BasicDefault(prefix, def)
}
// Ask is a wrapper around Terminal.Ask.
func Ask(question string) (bool, error) {
term, err := NewTerminal()
if err != nil {
return false, err
}
defer term.Close()
return term.Ask(question)
}
// Custom is a wrapper around Terminal.Custom.
func Custom(prefix string, test func(string) (string, bool)) (string, error) {
term, err := NewTerminal()
if err != nil {
return "", err
}
defer term.Close()
return term.Custom(prefix, test)
}
// Password is a wrapper around Terminal.Password.
func Password(prefix string) (string, error) {
term, err := NewTerminal()
if err != nil {
return "", err
}
defer term.Close()
return term.Password(prefix)
}

View File

@ -1,471 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"bufio"
"errors"
"io"
"os"
"strings"
)
var (
// ErrCTRLC is returned when CTRL+C is pressed stopping the prompt.
ErrCTRLC = errors.New("Interrupted (CTRL+C)")
// ErrEOF is returned when CTRL+D is pressed stopping the prompt.
ErrEOF = errors.New("EOF (CTRL+D)")
)
// Possible events that may occur when reading from input.
const (
evChar = iota
evSkip
evReturn
evEOF
evCtrlC
evBack
evClear
evHome
evEnd
evUp
evDown
evRight
evLeft
evDel
)
// IsNotTerminal checks if an error is related to the input not being a terminal.
func IsNotTerminal(err error) bool {
return isNotTerminal(err)
}
// TerminalSize retrieves the columns/rows for the terminal connected to out.
func TerminalSize(out *os.File) (int, int, error) {
return terminalSize(out)
}
// Terminal contains the state for raw terminal input.
type Terminal struct {
In *os.File
Out *os.File
History []string
histIdx int
simpleReader *bufio.Reader
t *terminal
}
// NewTerminal creates a terminal and sets it to raw input mode.
func NewTerminal() (*Terminal, error) {
in := os.Stdin
term, err := newTerminal(in)
if err != nil {
return nil, err
}
return &Terminal{
In: in,
Out: os.Stdout,
History: make([]string, 0, 10),
histIdx: -1,
t: term,
}, nil
}
// Basic gets input and if required tests to ensure input was given.
func (term *Terminal) Basic(prefix string, required bool) (string, error) {
return term.Custom(prefix, func(input string) (string, bool) {
if required && input == "" {
return "", false
}
return input, true
})
}
// BasicDefault gets input and if empty uses the given default.
func (term *Terminal) BasicDefault(prefix, def string) (string, error) {
return term.Custom(prefix+"(Default: "+def+")", func(input string) (string, bool) {
if input == "" {
input = def
}
return input, true
})
}
// Ask gets input and checks if it's truthy or not, and returns that
// in a boolean fashion.
func (term *Terminal) Ask(question string) (bool, error) {
input, err := term.Custom(question+"?(y/n)", func(input string) (string, bool) {
if input == "" {
return "", false
}
input = strings.ToLower(input)
if input == "y" || input == "yes" {
return "yes", true
}
return "", true
})
var ok bool
if input != "" {
ok = true
}
return ok, err
}
// Custom gets input and calls the given test function with the input to
// check if the input is valid, a true return will return the string.
func (term *Terminal) Custom(prefix string, test func(string) (string, bool)) (string, error) {
var err error
var input string
var ok bool
for !ok {
input, err = term.GetPrompt(prefix)
if err != nil && err != io.EOF {
return "", err
}
input, ok = test(input)
}
return input, nil
}
// Password retrieves a password from stdin without echoing it.
func (term *Terminal) Password(prefix string) (string, error) {
var err error
var input string
for input == "" {
input, err = term.GetPassword(prefix)
if err != nil && err != io.EOF {
return "", err
}
}
return input, nil
}
// GetPrompt gets a line with the prefix and echos input.
func (term *Terminal) GetPrompt(prefix string) (string, error) {
if !term.t.supportsEditing {
return term.simplePrompt(prefix)
}
buf := NewBuffer(prefix, term.Out, true)
return term.prompt(buf, NewAnsiReader(term.In))
}
// GetPassword gets a line with the prefix and doesn't echo input.
func (term *Terminal) GetPassword(prefix string) (string, error) {
if !term.t.supportsEditing {
return term.simplePrompt(prefix)
}
buf := NewBuffer(prefix, term.Out, false)
return term.password(buf, NewAnsiReader(term.In))
}
func (term *Terminal) Close() error {
return term.t.Close()
}
// simplePrompt is a fallback prompt without line editing support.
func (term *Terminal) simplePrompt(prefix string) (string, error) {
if term.simpleReader == nil {
term.simpleReader = bufio.NewReader(term.In)
}
_, err := term.Out.Write([]byte(prefix))
if err != nil {
return "", err
}
line, err := term.simpleReader.ReadString('\n')
line = strings.TrimRight(line, "\r\n ")
line = strings.TrimLeft(line, " ")
return line, err
}
// setup initializes a prompt.
func (term *Terminal) setup(buf *Buffer, in io.Reader) (*bufio.Reader, error) {
cols, _, err := TerminalSize(buf.Out)
if err != nil {
return nil, err
}
buf.Cols = cols
input := bufio.NewReader(in)
err = buf.Refresh()
if err != nil {
return nil, err
}
return input, nil
}
// read reads a rune and parses ANSI escape sequences found
func (term *Terminal) read(in *bufio.Reader) (int, rune, error) {
char, _, err := in.ReadRune()
if err != nil {
return 0, 0, err
}
switch char {
default:
// Standard chars.
return evChar, char, nil
case tabKey, ctrlA, ctrlB, ctrlE, ctrlF, ctrlG, ctrlH, ctrlJ, ctrlK, ctrlN,
ctrlO, ctrlP, ctrlQ, ctrlR, ctrlS, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX,
ctrlY, ctrlZ:
// Skip.
return evSkip, char, nil
case returnKey:
// End of line.
return evReturn, char, nil
case ctrlD:
// End of file.
return evEOF, char, nil
case ctrlC:
// End of line, interrupted.
return evCtrlC, char, nil
case backKey:
// Backspace.
return evBack, char, nil
case ctrlL:
// Clear screen.
return evClear, char, nil
case escKey:
// Functions like arrows, home, etc.
esc := make([]byte, 2)
_, err = in.Read(esc)
if err != nil {
return -1, char, err
}
// Home, end.
if esc[0] == 'O' {
switch esc[1] {
case 'H':
// Home.
return evHome, char, nil
case 'F':
// End.
return evEnd, char, nil
}
return evSkip, char, nil
}
// Arrows, delete, pgup, pgdown, insert.
if esc[0] == '[' {
switch esc[1] {
case 'A':
// Up.
return evUp, char, nil
case 'B':
// Down.
return evDown, char, nil
case 'C':
// Right.
return evRight, char, nil
case 'D':
// Left.
return evLeft, char, nil
}
// Delete, pgup, pgdown, insert.
if esc[1] > '0' && esc[1] < '7' {
extEsc := make([]byte, 3)
_, err = in.Read(extEsc)
if err != nil {
return -1, char, err
}
if extEsc[0] == '~' {
switch esc[1] {
case '2', '5', '6':
// Insert, pgup, pgdown.
return evSkip, char, err
case '3':
// Delete.
return evDel, char, err
}
}
}
}
}
return evSkip, char, nil
}
// prompt reads from in and parses ANSI escapes writing to buf.
func (term *Terminal) prompt(buf *Buffer, in io.Reader) (string, error) {
input, err := term.setup(buf, in)
if err != nil {
return "", err
}
term.History = append(term.History, "")
term.histIdx = len(term.History) - 1
curHistIdx := term.histIdx
for {
typ, char, err := term.read(input)
if err != nil {
return buf.String(), err
}
switch typ {
case evChar:
err = buf.Insert(char)
if err != nil {
return buf.String(), err
}
term.History[curHistIdx] = buf.String()
case evSkip:
continue
case evReturn:
err = buf.EndLine()
return buf.String(), err
case evEOF:
err = buf.EndLine()
if err == nil {
err = ErrEOF
}
return buf.String(), err
case evCtrlC:
err = buf.EndLine()
if err == nil {
err = ErrCTRLC
}
return buf.String(), err
case evBack:
err = buf.DelLeft()
if err != nil {
return buf.String(), err
}
term.History[curHistIdx] = buf.String()
case evClear:
err = buf.ClsScreen()
if err != nil {
return buf.String(), err
}
case evHome:
err = buf.Start()
if err != nil {
return buf.String(), err
}
case evEnd:
err = buf.End()
if err != nil {
return buf.String(), err
}
case evUp:
idx := term.histIdx
if term.histIdx > 0 {
idx--
}
err = buf.Set([]rune(term.History[idx])...)
if err != nil {
return buf.String(), err
}
term.histIdx = idx
case evDown:
idx := term.histIdx
if term.histIdx < len(term.History)-1 {
idx++
}
err = buf.Set([]rune(term.History[idx])...)
if err != nil {
return buf.String(), err
}
term.histIdx = idx
case evRight:
err = buf.Right()
if err != nil {
return buf.String(), err
}
case evLeft:
err = buf.Left()
if err != nil {
return buf.String(), err
}
case evDel:
err = buf.Del()
if err != nil {
return buf.String(), err
}
term.History[curHistIdx] = buf.String()
}
}
}
// password reads from in and parses restricted ANSI escapes writing to buf.
func (term *Terminal) password(buf *Buffer, in io.Reader) (string, error) {
input, err := term.setup(buf, in)
if err != nil {
return "", err
}
for {
typ, char, err := term.read(input)
if err != nil {
return buf.String(), err
}
switch typ {
case evChar:
err = buf.Insert(char)
if err != nil {
return buf.String(), err
}
case evSkip, evHome, evEnd, evUp, evDown, evRight, evLeft, evDel:
continue
case evReturn:
err = buf.EndLine()
return buf.String(), err
case evEOF:
err = buf.EndLine()
if err == nil {
err = ErrEOF
}
return buf.String(), err
case evCtrlC:
err = buf.EndLine()
if err == nil {
err = ErrCTRLC
}
return buf.String(), err
case evBack:
err = buf.DelLeft()
if err != nil {
return buf.String(), err
}
case evClear:
err = buf.ClsScreen()
if err != nil {
return buf.String(), err
}
}
}
}

View File

@ -1,96 +0,0 @@
// +build linux darwin freebsd openbsd netbsd dragonfly solaris
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"os"
"golang.org/x/sys/unix"
)
// List of unsupported $TERM values.
var unsupported = []string{"", "dumb", "cons25"}
// supportsEditing checks if the terminal supports ansi escapes.
func supportsEditing() bool {
term := os.Getenv("TERM")
for _, t := range unsupported {
if t == term {
return false
}
}
return true
}
// isNotTerminal checks if an error is related to the input not being a terminal.
func isNotTerminal(err error) bool {
return err == unix.ENOTTY
}
// terminal contains the private fields for a Unix terminal.
type terminal struct {
supportsEditing bool
fd uintptr
origMode unix.Termios
}
// newTerminal creates a terminal and sets it to raw input mode.
func newTerminal(in *os.File) (*terminal, error) {
term := &terminal{fd: in.Fd()}
if !supportsEditing() {
return term, nil
}
t, err := getTermios(term.fd)
if err != nil {
if IsNotTerminal(err) {
return term, nil
}
return nil, err
}
term.origMode = *t
mode := term.origMode
term.supportsEditing = true
// Set new mode flags, for reference see cfmakeraw(3).
mode.Iflag &^= (unix.BRKINT | unix.IGNBRK | unix.ICRNL |
unix.INLCR | unix.IGNCR | unix.ISTRIP | unix.IXON |
unix.PARMRK)
mode.Oflag &^= unix.OPOST
mode.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON |
unix.ISIG | unix.IEXTEN)
mode.Cflag &^= (unix.CSIZE | unix.PARENB)
mode.Cflag |= unix.CS8
// Set controls; min num of bytes, and timeouts.
mode.Cc[unix.VMIN] = 1
mode.Cc[unix.VTIME] = 0
err = setTermios(term.fd, true, &mode)
if err != nil {
return nil, err
}
return term, nil
}
// Close disables the terminals raw input.
func (term *terminal) Close() error {
if term.supportsEditing {
err := setTermios(term.fd, false, &term.origMode)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,116 +0,0 @@
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"os"
"syscall"
"unsafe"
)
// Flags to control the terminals mode.
const (
echoInputFlag = 0x0004
insertModeFlag = 0x0020
lineInputFlag = 0x0002
mouseInputFlag = 0x0010
processedInputFlag = 0x0001
windowInputFlag = 0x0008
)
// Error number returned for an invalid handle.
const errnoInvalidHandle = 0x6
var (
kernel = syscall.NewLazyDLL("kernel32.dll")
getConsoleScreenBufferInfo = kernel.NewProc("GetConsoleScreenBufferInfo")
setConsoleMode = kernel.NewProc("SetConsoleMode")
)
// consoleScreenBufferInfo contains various fields for the terminal.
type consoleScreenBufferInfo struct {
size coord
cursorPosition coord
attributes uint16
window smallRect
maximumWindowSize coord
}
// coord contains coords for positioning.
type coord struct {
x int16
y int16
}
// smallRect contains positions for the window edges.
type smallRect struct {
left int16
top int16
right int16
bottom int16
}
// terminalSize retrieves the cols/rows for the terminal connected to out.
func terminalSize(out *os.File) (int, int, error) {
csbi := new(consoleScreenBufferInfo)
ret, _, err := getConsoleScreenBufferInfo.Call(out.Fd(), uintptr(unsafe.Pointer(csbi)))
if ret == 0 {
return 0, 0, err
}
// Results are always off by one.
cols := csbi.window.right - csbi.window.left + 1
rows := csbi.window.bottom - csbi.window.top + 1
return int(cols), int(rows), nil
}
// isNotTerminal checks if an error is related to the input not being a terminal.
func isNotTerminal(err error) bool {
errno, ok := err.(syscall.Errno)
return ok && errno == errnoInvalidHandle
}
// terminal contains the private fields for a Windows terminal.
type terminal struct {
supportsEditing bool
fd uintptr
origMode uint32
}
// newTerminal creates a terminal and sets it to raw input mode.
func newTerminal(in *os.File) (*terminal, error) {
term := &terminal{fd: in.Fd()}
err := syscall.GetConsoleMode(syscall.Handle(term.fd), &term.origMode)
if err != nil {
return term, nil
}
mode := term.origMode
term.supportsEditing = true
// Set new mode flags.
mode &^= (echoInputFlag | insertModeFlag | lineInputFlag | mouseInputFlag |
processedInputFlag | windowInputFlag)
ret, _, err := setConsoleMode.Call(term.fd, uintptr(mode))
if ret == 0 {
return nil, err
}
return term, nil
}
// Close disables the terminals raw input.
func (term *terminal) Close() error {
if term.supportsEditing {
ret, _, err := setConsoleMode.Call(term.fd, uintptr(term.origMode))
if ret == 0 {
return err
}
}
return nil
}

View File

@ -1,23 +0,0 @@
# Docker version must be 17.05 or higher to allow multistage build
# See build and run instructions in README.md
# Builds Gas for utilization
FROM golang:1.8.1-alpine as builder
ENV workspace /go/src/github.com/GoASTScanner/gas
ENV GOPATH /go
COPY . $workspace
WORKDIR $workspace
RUN go vet $(go list ./... | grep -v /vendor/)
RUN CGO_ENABLED=0 go build -o gas .
########################################################
# Runs Gas on all Go files in the current directory when
# 'docker run' command in README is given
FROM alpine:3.6
COPY --from=builder /go/src/github.com/GoASTScanner/gas/gas /
# Mounted directory should be placed into the workdir
CMD /gas $(find . -path ./vendor -prune -o -type f -name "*.go")

View File

@ -1,154 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this
License, each Contributor hereby grants to You a perpetual, worldwide,
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
reproduce, prepare Derivative Works of, publicly display, publicly perform,
sublicense, and distribute the Work and such Derivative Works in Source or
Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License,
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section) patent
license to make, have made, use, offer to sell, sell, import, and otherwise
transfer the Work, where such license applies only to those patent claims
licensable by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s) with the Work
to which such Contribution(s) was submitted. If You institute patent litigation
against any entity (including a cross-claim or counterclaim in a lawsuit)
alleging that the Work or a Contribution incorporated within the Work
constitutes direct or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate as of the date
such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or
Derivative Works thereof in any medium, with or without modifications, and in
Source or Object form, provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and You must cause any modified files to carry prominent notices
stating that You changed the files; and You must retain, in the Source form of
any Derivative Works that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work, excluding those notices
that do not pertain to any part of the Derivative Works; and If the Work
includes a "NOTICE" text file as part of its distribution, then any Derivative
Works that You distribute must include a readable copy of the attribution
notices contained within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one of the following
places: within a NOTICE text file distributed as part of the Derivative Works;
within the Source form or documentation, if provided along with the Derivative
Works; or, within a display generated by the Derivative Works, if and wherever
such third-party notices normally appear. The contents of the NOTICE file are
for informational purposes only and do not modify the License. You may add Your
own attribution notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided that such
additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License. 5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks. This License does not grant permission to use the trade names,
trademarks, service marks, or product names of the Licensor, except as required
for reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
writing, Licensor provides the Work (and each Contributor provides its
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied, including, without limitation, any warranties
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any risks
associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in
tort (including negligence), contract, or otherwise, unless required by
applicable law (such as deliberate and grossly negligent acts) or agreed to in
writing, shall any Contributor be liable to You for damages, including any
direct, indirect, special, incidental, or consequential damages of any character
arising as a result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill, work stoppage,
computer failure or malfunction, or any and all other commercial damages or
losses), even if such Contributor has been advised of the possibility of such
damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or
Derivative Works thereof, You may choose to offer, and charge a fee for,
acceptance of support, warranty, indemnity, or other liability obligations
and/or rights consistent with this License. However, in accepting such
obligations, You may act only on Your own behalf and on Your sole
responsibility, not on behalf of any other Contributor, and only if You agree to
indemnify, defend, and hold each Contributor harmless for any liability incurred
by, or claims asserted against, such Contributor by reason of your accepting any
such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -1,134 +0,0 @@
## GAS - Go AST Scanner
Inspects source code for security problems by scanning the Go AST.
### License
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License [here](http://www.apache.org/licenses/LICENSE-2.0).
### Project status
[![Build Status](https://travis-ci.org/GoASTScanner/gas.svg?branch=master)](https://travis-ci.org/GoASTScanner/gas)
[![GoDoc](https://godoc.org/github.com/GoASTScanner/gas?status.svg)](https://godoc.org/github.com/GoASTScanner/gas)
Gas is still in alpha and accepting feedback from early adopters. We do
not consider it production ready at this time.
### Install
`$ go get github.com/GoASTScanner/gas/cmd/gas/...`
### Usage
Gas can be configured to only run a subset of rules, to exclude certain file
paths, and produce reports in different formats. By default all rules will be
run against the supplied input files. To recursively scan from the current
directory you can supply './...' as the input argument.
#### Selecting rules
By default Gas will run all rules against the supplied file paths. It is however possible to select a subset of rules to run via the '-include=' flag,
or to specify a set of rules to explicitly exclude using the '-exclude=' flag.
##### Available rules
- G101: Look for hardcoded credentials
- G102: Bind to all interfaces
- G103: Audit the use of unsafe block
- G104: Audit errors not checked
- G105: Audit the use of math/big.Int.Exp
- G106: Audit the use of ssh.InsecureIgnoreHostKey
- G201: SQL query construction using format string
- G202: SQL query construction using string concatenation
- G203: Use of unescaped data in HTML templates
- G204: Audit use of command execution
- G301: Poor file permissions used when creating a directory
- G302: Poor file permisions used with chmod
- G303: Creating tempfile using a predictable path
- G401: Detect the usage of DES, RC4, or MD5
- G402: Look for bad TLS connection settings
- G403: Ensure minimum RSA key length of 2048 bits
- G404: Insecure random number source (rand)
- G501: Import blacklist: crypto/md5
- G502: Import blacklist: crypto/des
- G503: Import blacklist: crypto/rc4
- G504: Import blacklist: net/http/cgi
```
# Run a specific set of rules
$ gas -include=G101,G203,G401 ./...
# Run everything except for rule G303
$ gas -exclude=G303 ./...
```
#### Excluding files:
Gas will ignore dependencies in your vendor directory any files
that are not considered build artifacts by the compiler (so test files).
#### Annotating code
As with all automated detection tools there will be cases of false positives. In cases where Gas reports a failure that has been manually verified as being safe it is possible to annotate the code with a '#nosec' comment.
The annotation causes Gas to stop processing any further nodes within the
AST so can apply to a whole block or more granularly to a single expression.
```go
import "md5" // #nosec
func main(){
/* #nosec */
if x > y {
h := md5.New() // this will also be ignored
}
}
```
In some cases you may also want to revisit places where #nosec annotations
have been used. To run the scanner and ignore any #nosec annotations you
can do the following:
```
$ gas -nosec=true ./...
```
### Output formats
Gas currently supports text, json, yaml, csv and JUnit XML output formats. By default
results will be reported to stdout, but can also be written to an output
file. The output format is controlled by the '-fmt' flag, and the output file is controlled by the '-out' flag as follows:
```
# Write output in json format to results.json
$ gas -fmt=json -out=results.json *.go
```
### Generate TLS rule
The configuration of TLS rule can be generated from [Mozilla's TLS ciphers recommendation](https://statics.tls.security.mozilla.org/server-side-tls-conf.json).
First you need to install the generator tool:
```
go get github.com/GoASTScanner/gas/cmd/tlsconfig/...
```
You can invoke now the `go generate` in the root of the project:
```
go generate ./...
```
This will generate the `rules/tls_config.go` file with will contain the current ciphers recommendation from Mozilla.

View File

@ -1,197 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package gas holds the central scanning logic used by GAS
package gas
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"go/types"
"log"
"os"
"path"
"reflect"
"strings"
"path/filepath"
"golang.org/x/tools/go/loader"
)
// The Context is populated with data parsed from the source code as it is scanned.
// It is passed through to all rule functions as they are called. Rules may use
// this data in conjunction withe the encoutered AST node.
type Context struct {
FileSet *token.FileSet
Comments ast.CommentMap
Info *types.Info
Pkg *types.Package
Root *ast.File
Config map[string]interface{}
Imports *ImportTracker
}
// Metrics used when reporting information about a scanning run.
type Metrics struct {
NumFiles int `json:"files"`
NumLines int `json:"lines"`
NumNosec int `json:"nosec"`
NumFound int `json:"found"`
}
// Analyzer object is the main object of GAS. It has methods traverse an AST
// and invoke the correct checking rules as on each node as required.
type Analyzer struct {
ignoreNosec bool
ruleset RuleSet
context *Context
config Config
logger *log.Logger
issues []*Issue
stats *Metrics
}
// NewAnalyzer builds a new anaylzer.
func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer {
ignoreNoSec := false
if setting, err := conf.GetGlobal("nosec"); err == nil {
ignoreNoSec = setting == "true" || setting == "enabled"
}
if logger == nil {
logger = log.New(os.Stderr, "[gas]", log.LstdFlags)
}
return &Analyzer{
ignoreNosec: ignoreNoSec,
ruleset: make(RuleSet),
context: &Context{},
config: conf,
logger: logger,
issues: make([]*Issue, 0, 16),
stats: &Metrics{},
}
}
// LoadRules instantiates all the rules to be used when analyzing source
// packages
func (gas *Analyzer) LoadRules(ruleDefinitions ...RuleBuilder) {
for _, builder := range ruleDefinitions {
r, nodes := builder(gas.config)
gas.ruleset.Register(r, nodes...)
}
}
// Process kicks off the analysis process for a given package
func (gas *Analyzer) Process(packagePaths ...string) error {
packageConfig := loader.Config{
Build: &build.Default,
ParserMode: parser.ParseComments,
AllowErrors: true,
}
for _, packagePath := range packagePaths {
abspath, err := filepath.Abs(packagePath)
if err != nil {
return err
}
gas.logger.Println("Searching directory:", abspath)
basePackage, err := build.Default.ImportDir(packagePath, build.ImportComment)
if err != nil {
return err
}
var packageFiles []string
for _, filename := range basePackage.GoFiles {
packageFiles = append(packageFiles, path.Join(packagePath, filename))
}
packageConfig.CreateFromFilenames(basePackage.Name, packageFiles...)
}
builtPackage, err := packageConfig.Load()
if err != nil {
return err
}
for _, pkg := range builtPackage.Created {
gas.logger.Println("Checking package:", pkg.String())
for _, file := range pkg.Files {
gas.logger.Println("Checking file:", builtPackage.Fset.File(file.Pos()).Name())
gas.context.FileSet = builtPackage.Fset
gas.context.Config = gas.config
gas.context.Comments = ast.NewCommentMap(gas.context.FileSet, file, file.Comments)
gas.context.Root = file
gas.context.Info = &pkg.Info
gas.context.Pkg = pkg.Pkg
gas.context.Imports = NewImportTracker()
gas.context.Imports.TrackPackages(gas.context.Pkg.Imports()...)
ast.Walk(gas, file)
gas.stats.NumFiles++
gas.stats.NumLines += builtPackage.Fset.File(file.Pos()).LineCount()
}
}
return nil
}
// ignore a node (and sub-tree) if it is tagged with a "#nosec" comment
func (gas *Analyzer) ignore(n ast.Node) bool {
if groups, ok := gas.context.Comments[n]; ok && !gas.ignoreNosec {
for _, group := range groups {
if strings.Contains(group.Text(), "#nosec") {
gas.stats.NumNosec++
return true
}
}
}
return false
}
// Visit runs the GAS visitor logic over an AST created by parsing go code.
// Rule methods added with AddRule will be invoked as necessary.
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
if !gas.ignore(n) {
// Track aliased and initialization imports
gas.context.Imports.TrackImport(n)
for _, rule := range gas.ruleset.RegisteredFor(n) {
issue, err := rule.Match(n, gas.context)
if err != nil {
file, line := GetLocation(n, gas.context)
file = path.Base(file)
gas.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
}
if issue != nil {
gas.issues = append(gas.issues, issue)
gas.stats.NumFound++
}
}
return gas
}
return nil
}
// Report returns the current issues discovered and the metrics about the scan
func (gas *Analyzer) Report() ([]*Issue, *Metrics) {
return gas.issues, gas.stats
}
// Reset clears state such as context, issues and metrics from the configured analyzer
func (gas *Analyzer) Reset() {
gas.context = &Context{}
gas.issues = make([]*Issue, 0, 16)
gas.stats = &Metrics{}
}

View File

@ -1,78 +0,0 @@
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"go/ast"
)
type set map[string]bool
// CallList is used to check for usage of specific packages
// and functions.
type CallList map[string]set
// NewCallList creates a new empty CallList
func NewCallList() CallList {
return make(CallList)
}
// AddAll will add several calls to the call list at once
func (c CallList) AddAll(selector string, idents ...string) {
for _, ident := range idents {
c.Add(selector, ident)
}
}
// Add a selector and call to the call list
func (c CallList) Add(selector, ident string) {
if _, ok := c[selector]; !ok {
c[selector] = make(set)
}
c[selector][ident] = true
}
// Contains returns true if the package and function are
/// members of this call list.
func (c CallList) Contains(selector, ident string) bool {
if idents, ok := c[selector]; ok {
_, found := idents[ident]
return found
}
return false
}
// ContainsCallExpr resolves the call expression name and type
/// or package and determines if it exists within the CallList
func (c CallList) ContainsCallExpr(n ast.Node, ctx *Context) *ast.CallExpr {
selector, ident, err := GetCallInfo(n, ctx)
if err != nil {
return nil
}
// Use only explicit path to reduce conflicts
if path, ok := GetImportPath(selector, ctx); ok && c.Contains(path, ident) {
return n.(*ast.CallExpr)
}
/*
// Try direct resolution
if c.Contains(selector, ident) {
log.Printf("c.Contains == true, %s, %s.", selector, ident)
return n.(*ast.CallExpr)
}
*/
return nil
}

View File

@ -1,87 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"sort"
"strings"
"github.com/ryanuber/go-glob"
)
// fileList uses a map for patterns to ensure each pattern only
// appears once
type fileList struct {
patterns map[string]struct{}
}
func newFileList(paths ...string) *fileList {
f := &fileList{
patterns: make(map[string]struct{}),
}
for _, p := range paths {
f.patterns[p] = struct{}{}
}
return f
}
func (f *fileList) String() string {
ps := make([]string, 0, len(f.patterns))
for p := range f.patterns {
ps = append(ps, p)
}
sort.Strings(ps)
return strings.Join(ps, ", ")
}
func (f *fileList) Set(path string) error {
if path == "" {
// don't bother adding the empty path
return nil
}
f.patterns[path] = struct{}{}
return nil
}
func (f fileList) Contains(path string) bool {
for p := range f.patterns {
if strings.Contains(p, glob.GLOB) {
if glob.Glob(p, path) {
if logger != nil {
logger.Printf("skipping: %s\n", path)
}
return true
}
} else {
// check if only a sub-folder of the path is excluded
if strings.Contains(path, p) {
if logger != nil {
logger.Printf("skipping: %s\n", path)
}
return true
}
}
}
return false
}
/*
func (f fileList) Dump() {
for k, _ := range f.paths {
println(k)
}
}
*/

View File

@ -1,254 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"log"
"os"
"regexp"
"sort"
"strings"
"github.com/GoASTScanner/gas"
"github.com/GoASTScanner/gas/output"
"github.com/GoASTScanner/gas/rules"
"github.com/kisielk/gotool"
)
const (
usageText = `
GAS - Go AST Scanner
Gas analyzes Go source code to look for common programming mistakes that
can lead to security problems.
USAGE:
# Check a single package
$ gas $GOPATH/src/github.com/example/project
# Check all packages under the current directory and save results in
# json format.
$ gas -fmt=json -out=results.json ./...
# Run a specific set of rules (by default all rules will be run):
$ gas -include=G101,G203,G401 ./...
# Run all rules except the provided
$ gas -exclude=G101 $GOPATH/src/github.com/example/project/...
`
)
var (
// #nosec flag
flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set")
// format output
flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, yaml, csv, junit-xml, html, or text")
// output file
flagOutput = flag.String("out", "", "Set output file for results")
// config file
flagConfig = flag.String("conf", "", "Path to optional config file")
// quiet
flagQuiet = flag.Bool("quiet", false, "Only show output when errors are found")
// rules to explicitly include
flagRulesInclude = flag.String("include", "", "Comma separated list of rules IDs to include. (see rule list)")
// rules to explicitly exclude
flagRulesExclude = flag.String("exclude", "", "Comma separated list of rules IDs to exclude. (see rule list)")
// log to file or stderr
flagLogfile = flag.String("log", "", "Log messages to file rather than stderr")
// sort the issues by severity
flagSortIssues = flag.Bool("sort", true, "Sort issues by severity")
logger *log.Logger
)
// #nosec
func usage() {
fmt.Fprintln(os.Stderr, usageText)
fmt.Fprint(os.Stderr, "OPTIONS:\n\n")
flag.PrintDefaults()
fmt.Fprint(os.Stderr, "\n\nRULES:\n\n")
// sorted rule list for ease of reading
rl := rules.Generate()
keys := make([]string, 0, len(rl))
for key := range rl {
keys = append(keys, key)
}
sort.Strings(keys)
for _, k := range keys {
v := rl[k]
fmt.Fprintf(os.Stderr, "\t%s: %s\n", k, v.Description)
}
fmt.Fprint(os.Stderr, "\n")
}
func loadConfig(configFile string) (gas.Config, error) {
config := gas.NewConfig()
if configFile != "" {
file, err := os.Open(configFile)
if err != nil {
return nil, err
}
defer file.Close()
if _, err := config.ReadFrom(file); err != nil {
return nil, err
}
}
if *flagIgnoreNoSec {
config.SetGlobal("nosec", "true")
}
return config, nil
}
func loadRules(include, exclude string) rules.RuleList {
var filters []rules.RuleFilter
if include != "" {
logger.Printf("including rules: %s", include)
including := strings.Split(include, ",")
filters = append(filters, rules.NewRuleFilter(false, including...))
} else {
logger.Println("including rules: default")
}
if exclude != "" {
logger.Printf("excluding rules: %s", exclude)
excluding := strings.Split(exclude, ",")
filters = append(filters, rules.NewRuleFilter(true, excluding...))
} else {
logger.Println("excluding rules: default")
}
return rules.Generate(filters...)
}
func saveOutput(filename, format string, issues []*gas.Issue, metrics *gas.Metrics) error {
if filename != "" {
outfile, err := os.Create(filename)
if err != nil {
return err
}
defer outfile.Close()
err = output.CreateReport(outfile, format, issues, metrics)
if err != nil {
return err
}
} else {
err := output.CreateReport(os.Stdout, format, issues, metrics)
if err != nil {
return err
}
}
return nil
}
func main() {
// Setup usage description
flag.Usage = usage
// Parse command line arguments
flag.Parse()
// Ensure at least one file was specified
if flag.NArg() == 0 {
fmt.Fprintf(os.Stderr, "\nError: FILE [FILE...] or './...' expected\n") // #nosec
flag.Usage()
os.Exit(1)
}
// Setup logging
logWriter := os.Stderr
if *flagLogfile != "" {
var e error
logWriter, e = os.Create(*flagLogfile)
if e != nil {
flag.Usage()
log.Fatal(e)
}
}
logger = log.New(logWriter, "[gas] ", log.LstdFlags)
// Load config
config, err := loadConfig(*flagConfig)
if err != nil {
logger.Fatal(err)
}
// Load enabled rule definitions
ruleDefinitions := loadRules(*flagRulesInclude, *flagRulesExclude)
if len(ruleDefinitions) <= 0 {
logger.Fatal("cannot continue: no rules are configured.")
}
// Create the analyzer
analyzer := gas.NewAnalyzer(config, logger)
analyzer.LoadRules(ruleDefinitions.Builders()...)
vendor := regexp.MustCompile(`[\\/]vendor([\\/]|$)`)
var packages []string
// Iterate over packages on the import paths
for _, pkg := range gotool.ImportPaths(flag.Args()) {
// Skip vendor directory
if vendor.MatchString(pkg) {
continue
}
packages = append(packages, pkg)
}
if err := analyzer.Process(packages...); err != nil {
logger.Fatal(err)
}
// Collect the results
issues, metrics := analyzer.Report()
issuesFound := len(issues) > 0
// Exit quietly if nothing was found
if !issuesFound && *flagQuiet {
os.Exit(0)
}
// Sort the issue by severity
if *flagSortIssues {
sortIssues(issues)
}
// Create output report
if err := saveOutput(*flagOutput, *flagFormat, issues, metrics); err != nil {
logger.Fatal(err)
}
// Finialize logging
logWriter.Close() // #nosec
// Do we have an issue? If so exit 1
if issuesFound {
os.Exit(1)
}
}

View File

@ -1,20 +0,0 @@
package main
import (
"sort"
"github.com/GoASTScanner/gas"
)
type sortBySeverity []*gas.Issue
func (s sortBySeverity) Len() int { return len(s) }
func (s sortBySeverity) Less(i, j int) bool { return s[i].Severity > s[i].Severity }
func (s sortBySeverity) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// sortIssues sorts the issues by severity in descending order
func sortIssues(issues []*gas.Issue) {
sort.Sort(sortBySeverity(issues))
}

View File

@ -1,88 +0,0 @@
package gas
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
)
const (
// Globals are applicable to all rules and used for general
// configuration settings for gas.
Globals = "global"
)
// Config is used to provide configuration and customization to each of the rules.
type Config map[string]interface{}
// NewConfig initializes a new configuration instance. The configuration data then
// needs to be loaded via c.ReadFrom(strings.NewReader("config data"))
// or from a *os.File.
func NewConfig() Config {
cfg := make(Config)
cfg[Globals] = make(map[string]string)
return cfg
}
// ReadFrom implements the io.ReaderFrom interface. This
// should be used with io.Reader to load configuration from
//file or from string etc.
func (c Config) ReadFrom(r io.Reader) (int64, error) {
data, err := ioutil.ReadAll(r)
if err != nil {
return int64(len(data)), err
}
if err = json.Unmarshal(data, &c); err != nil {
return int64(len(data)), err
}
return int64(len(data)), nil
}
// WriteTo implements the io.WriteTo interface. This should
// be used to save or print out the configuration information.
func (c Config) WriteTo(w io.Writer) (int64, error) {
data, err := json.Marshal(c)
if err != nil {
return int64(len(data)), err
}
return io.Copy(w, bytes.NewReader(data))
}
// Get returns the configuration section for the supplied key
func (c Config) Get(section string) (interface{}, error) {
settings, found := c[section]
if !found {
return nil, fmt.Errorf("Section %s not in configuration", section)
}
return settings, nil
}
// Set section in the configuration to specified value
func (c Config) Set(section string, value interface{}) {
c[section] = value
}
// GetGlobal returns value associated with global configuration option
func (c Config) GetGlobal(option string) (string, error) {
if globals, ok := c[Globals]; ok {
if settings, ok := globals.(map[string]string); ok {
if value, ok := settings[option]; ok {
return value, nil
}
return "", fmt.Errorf("global setting for %s not found", option)
}
}
return "", fmt.Errorf("no global config options found")
}
// SetGlobal associates a value with a global configuration ooption
func (c Config) SetGlobal(option, value string) {
if globals, ok := c[Globals]; ok {
if settings, ok := globals.(map[string]string); ok {
settings[option] = value
}
}
}

View File

@ -1,195 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strconv"
)
// MatchCallByPackage ensures that the specified package is imported,
// adjusts the name for any aliases and ignores cases that are
// initialization only imports.
//
// Usage:
// node, matched := MatchCallByPackage(n, ctx, "math/rand", "Read")
//
func MatchCallByPackage(n ast.Node, c *Context, pkg string, names ...string) (*ast.CallExpr, bool) {
importedName, found := GetImportedName(pkg, c)
if !found {
return nil, false
}
if callExpr, ok := n.(*ast.CallExpr); ok {
packageName, callName, err := GetCallInfo(callExpr, c)
if err != nil {
return nil, false
}
if packageName == importedName {
for _, name := range names {
if callName == name {
return callExpr, true
}
}
}
}
return nil, false
}
// MatchCallByType ensures that the node is a call expression to a
// specific object type.
//
// Usage:
// node, matched := MatchCallByType(n, ctx, "bytes.Buffer", "WriteTo", "Write")
//
func MatchCallByType(n ast.Node, ctx *Context, requiredType string, calls ...string) (*ast.CallExpr, bool) {
if callExpr, ok := n.(*ast.CallExpr); ok {
typeName, callName, err := GetCallInfo(callExpr, ctx)
if err != nil {
return nil, false
}
if typeName == requiredType {
for _, call := range calls {
if call == callName {
return callExpr, true
}
}
}
}
return nil, false
}
// MatchCompLit will match an ast.CompositeLit based on the supplied type
func MatchCompLit(n ast.Node, ctx *Context, required string) *ast.CompositeLit {
if complit, ok := n.(*ast.CompositeLit); ok {
typeOf := ctx.Info.TypeOf(complit)
if typeOf.String() == required {
return complit
}
}
return nil
}
// GetInt will read and return an integer value from an ast.BasicLit
func GetInt(n ast.Node) (int64, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.INT {
return strconv.ParseInt(node.Value, 0, 64)
}
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetFloat will read and return a float value from an ast.BasicLit
func GetFloat(n ast.Node) (float64, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.FLOAT {
return strconv.ParseFloat(node.Value, 64)
}
return 0.0, fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetChar will read and return a char value from an ast.BasicLit
func GetChar(n ast.Node) (byte, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.CHAR {
return node.Value[0], nil
}
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetString will read and return a string value from an ast.BasicLit
func GetString(n ast.Node) (string, error) {
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING {
return strconv.Unquote(node.Value)
}
return "", fmt.Errorf("Unexpected AST node type: %T", n)
}
// GetCallObject returns the object and call expression and associated
// object for a given AST node. nil, nil will be returned if the
// object cannot be resolved.
func GetCallObject(n ast.Node, ctx *Context) (*ast.CallExpr, types.Object) {
switch node := n.(type) {
case *ast.CallExpr:
switch fn := node.Fun.(type) {
case *ast.Ident:
return node, ctx.Info.Uses[fn]
case *ast.SelectorExpr:
return node, ctx.Info.Uses[fn.Sel]
}
}
return nil, nil
}
// GetCallInfo returns the package or type and name associated with a
// call expression.
func GetCallInfo(n ast.Node, ctx *Context) (string, string, error) {
switch node := n.(type) {
case *ast.CallExpr:
switch fn := node.Fun.(type) {
case *ast.SelectorExpr:
switch expr := fn.X.(type) {
case *ast.Ident:
if expr.Obj != nil && expr.Obj.Kind == ast.Var {
t := ctx.Info.TypeOf(expr)
if t != nil {
return t.String(), fn.Sel.Name, nil
}
return "undefined", fn.Sel.Name, fmt.Errorf("missing type info")
}
return expr.Name, fn.Sel.Name, nil
}
case *ast.Ident:
return ctx.Pkg.Name(), fn.Name, nil
}
}
return "", "", fmt.Errorf("unable to determine call info")
}
// GetImportedName returns the name used for the package within the
// code. It will resolve aliases and ignores initalization only imports.
func GetImportedName(path string, ctx *Context) (string, bool) {
importName, imported := ctx.Imports.Imported[path]
if !imported {
return "", false
}
if _, initonly := ctx.Imports.InitOnly[path]; initonly {
return "", false
}
if alias, ok := ctx.Imports.Aliased[path]; ok {
importName = alias
}
return importName, true
}
// GetImportPath resolves the full import path of an identifer based on
// the imports in the current context.
func GetImportPath(name string, ctx *Context) (string, bool) {
for path := range ctx.Imports.Imported {
if imported, ok := GetImportedName(path, ctx); ok && imported == name {
return path, true
}
}
return "", false
}
// GetLocation returns the filename and line number of an ast.Node
func GetLocation(n ast.Node, ctx *Context) (string, int) {
fobj := ctx.FileSet.File(n.Pos())
return fobj.Name(), fobj.Line(n.Pos())
}

View File

@ -1,67 +0,0 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"go/ast"
"go/types"
"strings"
)
// ImportTracker is used to normalize the packages that have been imported
// by a source file. It is able to differentiate between plain imports, aliased
// imports and init only imports.
type ImportTracker struct {
Imported map[string]string
Aliased map[string]string
InitOnly map[string]bool
}
// NewImportTracker creates an empty Import tracker instance
func NewImportTracker() *ImportTracker {
return &ImportTracker{
make(map[string]string),
make(map[string]string),
make(map[string]bool),
}
}
// TrackPackages tracks all the imports used by the supplied packages
func (t *ImportTracker) TrackPackages(pkgs ...*types.Package) {
for _, pkg := range pkgs {
t.Imported[pkg.Path()] = pkg.Name()
// Transient imports
//for _, imp := range pkg.Imports() {
// t.Imported[imp.Path()] = imp.Name()
//}
}
}
// TrackImport tracks imports and handles the 'unsafe' import
func (t *ImportTracker) TrackImport(n ast.Node) {
if imported, ok := n.(*ast.ImportSpec); ok {
path := strings.Trim(imported.Path.Value, `"`)
if imported.Name != nil {
if imported.Name.Name == "_" {
// Initialization only import
t.InitOnly[path] = true
} else {
// Aliased import
t.Aliased[path] = imported.Name.Name
}
}
if path == "unsafe" {
t.Imported[path] = path
}
}
}

View File

@ -1,118 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"encoding/json"
"fmt"
"go/ast"
"os"
"strconv"
)
// Score type used by severity and confidence values
type Score int
const (
// Low severity or confidence
Low Score = iota
// Medium severity or confidence
Medium
// High severity or confidence
High
)
// Issue is returnd by a GAS rule if it discovers an issue with the scanned code.
type Issue struct {
Severity Score `json:"severity"` // issue severity (how problematic it is)
Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it)
What string `json:"details"` // Human readable explanation
File string `json:"file"` // File name we found it in
Code string `json:"code"` // Impacted code line
Line string `json:"line"` // Line number in file
}
// MetaData is embedded in all GAS rules. The Severity, Confidence and What message
// will be passed tbhrough to reported issues.
type MetaData struct {
Severity Score
Confidence Score
What string
}
// MarshalJSON is used convert a Score object into a JSON representation
func (c Score) MarshalJSON() ([]byte, error) {
return json.Marshal(c.String())
}
// String converts a Score into a string
func (c Score) String() string {
switch c {
case High:
return "HIGH"
case Medium:
return "MEDIUM"
case Low:
return "LOW"
}
return "UNDEFINED"
}
func codeSnippet(file *os.File, start int64, end int64, n ast.Node) (string, error) {
if n == nil {
return "", fmt.Errorf("Invalid AST node provided")
}
size := (int)(end - start) // Go bug, os.File.Read should return int64 ...
file.Seek(start, 0) // #nosec
buf := make([]byte, size)
if nread, err := file.Read(buf); err != nil || nread != size {
return "", fmt.Errorf("Unable to read code")
}
return string(buf), nil
}
// NewIssue creates a new Issue
func NewIssue(ctx *Context, node ast.Node, desc string, severity Score, confidence Score) *Issue {
var code string
fobj := ctx.FileSet.File(node.Pos())
name := fobj.Name()
start, end := fobj.Line(node.Pos()), fobj.Line(node.End())
line := strconv.Itoa(start)
if start != end {
line = fmt.Sprintf("%d-%d", start, end)
}
if file, err := os.Open(fobj.Name()); err == nil {
defer file.Close()
s := (int64)(fobj.Position(node.Pos()).Offset) // Go bug, should be int64
e := (int64)(fobj.Position(node.End()).Offset) // Go bug, should be int64
code, err = codeSnippet(file, s, e, node)
if err != nil {
code = err.Error()
}
}
return &Issue{
File: name,
Line: line,
What: desc,
Confidence: confidence,
Severity: severity,
Code: code,
}
}

View File

@ -1,168 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
import (
"encoding/csv"
"encoding/json"
"encoding/xml"
htmlTemplate "html/template"
"io"
plainTemplate "text/template"
"github.com/GoASTScanner/gas"
"gopkg.in/yaml.v2"
)
// ReportFormat enumrates the output format for reported issues
type ReportFormat int
const (
// ReportText is the default format that writes to stdout
ReportText ReportFormat = iota // Plain text format
// ReportJSON set the output format to json
ReportJSON // Json format
// ReportCSV set the output format to csv
ReportCSV // CSV format
// ReportJUnitXML set the output format to junit xml
ReportJUnitXML // JUnit XML format
)
var text = `Results:
{{ range $index, $issue := .Issues }}
[{{ $issue.File }}:{{ $issue.Line }}] - {{ $issue.What }} (Confidence: {{ $issue.Confidence}}, Severity: {{ $issue.Severity }})
> {{ $issue.Code }}
{{ end }}
Summary:
Files: {{.Stats.NumFiles}}
Lines: {{.Stats.NumLines}}
Nosec: {{.Stats.NumNosec}}
Issues: {{.Stats.NumFound}}
`
type reportInfo struct {
Issues []*gas.Issue
Stats *gas.Metrics
}
// CreateReport generates a report based for the supplied issues and metrics given
// the specified format. The formats currently accepted are: json, csv, html and text.
func CreateReport(w io.Writer, format string, issues []*gas.Issue, metrics *gas.Metrics) error {
data := &reportInfo{
Issues: issues,
Stats: metrics,
}
var err error
switch format {
case "json":
err = reportJSON(w, data)
case "yaml":
err = reportYAML(w, data)
case "csv":
err = reportCSV(w, data)
case "junit-xml":
err = reportJUnitXML(w, data)
case "html":
err = reportFromHTMLTemplate(w, html, data)
case "text":
err = reportFromPlaintextTemplate(w, text, data)
default:
err = reportFromPlaintextTemplate(w, text, data)
}
return err
}
func reportJSON(w io.Writer, data *reportInfo) error {
raw, err := json.MarshalIndent(data, "", "\t")
if err != nil {
panic(err)
}
_, err = w.Write(raw)
if err != nil {
panic(err)
}
return err
}
func reportYAML(w io.Writer, data *reportInfo) error {
raw, err := yaml.Marshal(data)
if err != nil {
return err
}
_, err = w.Write(raw)
return err
}
func reportCSV(w io.Writer, data *reportInfo) error {
out := csv.NewWriter(w)
defer out.Flush()
for _, issue := range data.Issues {
err := out.Write([]string{
issue.File,
issue.Line,
issue.What,
issue.Severity.String(),
issue.Confidence.String(),
issue.Code,
})
if err != nil {
return err
}
}
return nil
}
func reportJUnitXML(w io.Writer, data *reportInfo) error {
groupedData := groupDataByRules(data)
junitXMLStruct := createJUnitXMLStruct(groupedData)
raw, err := xml.MarshalIndent(junitXMLStruct, "", "\t")
if err != nil {
return err
}
xmlHeader := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
raw = append(xmlHeader, raw...)
_, err = w.Write(raw)
if err != nil {
return err
}
return nil
}
func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, data *reportInfo) error {
t, e := plainTemplate.New("gas").Parse(reportTemplate)
if e != nil {
return e
}
return t.Execute(w, data)
}
func reportFromHTMLTemplate(w io.Writer, reportTemplate string, data *reportInfo) error {
t, e := htmlTemplate.New("gas").Parse(reportTemplate)
if e != nil {
return e
}
return t.Execute(w, data)
}

View File

@ -1,74 +0,0 @@
package output
import (
"encoding/xml"
htmlLib "html"
"strconv"
"github.com/GoASTScanner/gas"
)
type junitXMLReport struct {
XMLName xml.Name `xml:"testsuites"`
Testsuites []testsuite `xml:"testsuite"`
}
type testsuite struct {
XMLName xml.Name `xml:"testsuite"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Testcases []testcase `xml:"testcase"`
}
type testcase struct {
XMLName xml.Name `xml:"testcase"`
Name string `xml:"name,attr"`
Failure failure `xml:"failure"`
}
type failure struct {
XMLName xml.Name `xml:"failure"`
Message string `xml:"message,attr"`
Text string `xml:",innerxml"`
}
func generatePlaintext(issue *gas.Issue) string {
return "Results:\n" +
"[" + issue.File + ":" + issue.Line + "] - " +
issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) +
", Severity: " + strconv.Itoa(int(issue.Severity)) + ")\n" + "> " + htmlLib.EscapeString(issue.Code)
}
func groupDataByRules(data *reportInfo) map[string][]*gas.Issue {
groupedData := make(map[string][]*gas.Issue)
for _, issue := range data.Issues {
if _, ok := groupedData[issue.What]; ok {
groupedData[issue.What] = append(groupedData[issue.What], issue)
} else {
groupedData[issue.What] = []*gas.Issue{issue}
}
}
return groupedData
}
func createJUnitXMLStruct(groupedData map[string][]*gas.Issue) junitXMLReport {
var xmlReport junitXMLReport
for what, issues := range groupedData {
testsuite := testsuite{
Name: what,
Tests: len(issues),
}
for _, issue := range issues {
testcase := testcase{
Name: issue.File,
Failure: failure{
Message: "Found 1 vulnerability. See stacktrace for details.",
Text: generatePlaintext(issue),
},
}
testsuite.Testcases = append(testsuite.Testcases, testcase)
}
xmlReport.Testsuites = append(xmlReport.Testsuites, testsuite)
}
return xmlReport
}

View File

@ -1,401 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package output
const html = `
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Go AST Scanner</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.2.1/css/bulma.min.css" integrity="sha256-DRcOKg8NK1KkSkcymcGmxOtS/lAn0lHWJXRa15gMHHk=" crossorigin="anonymous"/>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.min.js" integrity="sha256-cLWs9L+cjZg8CjGHMpJqUgKKouPlmoMP/0wIdPtaPGs=" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.min.js" integrity="sha256-JIW8lNqN2EtqC6ggNZYnAdKMJXRQfkPMvdRt+b0/Jxc=" crossorigin="anonymous"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.17.0/babel.min.js" integrity="sha256-1IWWLlCKFGFj/cjryvC7GDF5wRYnf9tSvNVVEj8Bm+o=" crossorigin="anonymous"></script>
<style>
div.issue div.tag, div.panel-block input[type="checkbox"] {
margin-right: 0.5em;
}
label.disabled {
text-decoration: line-through;
}
nav.panel select {
width: 100%;
}
.break-word {
word-wrap: break-word;
}
</style>
</head>
<body>
<section class="section">
<div class="container">
<div id="content"></div>
</div>
</section>
<script>
var data = {{ . }};
</script>
<script type="text/babel">
var IssueTag = React.createClass({
render: function() {
var level = ""
if (this.props.level === "HIGH") {
level = "is-danger";
}
if (this.props.level === "MEDIUM") {
level = "is-warning";
}
return (
<div className={ "tag " + level }>
{ this.props.label }: { this.props.level }
</div>
);
}
});
var Issue = React.createClass({
render: function() {
return (
<div className="issue box">
<div className="is-pulled-right">
<IssueTag label="Severity" level={ this.props.data.severity }/>
<IssueTag label="Confidence" level={ this.props.data.confidence }/>
</div>
<p>
<strong className="break-word">
{ this.props.data.file } (line { this.props.data.line })
</strong>
<br/>
{ this.props.data.details }
</p>
<figure className="highlight">
<pre>
<code className="golang hljs">
{ this.props.data.code }
</code>
</pre>
</figure>
</div>
);
}
});
var Stats = React.createClass({
render: function() {
return (
<p className="help">
Scanned { this.props.data.metrics.files.toLocaleString() } files
with { this.props.data.metrics.lines.toLocaleString() } lines of code.
</p>
);
}
});
var Issues = React.createClass({
render: function() {
if (this.props.data.metrics.files === 0) {
return (
<div className="notification">
No source files found. Do you even Go?
</div>
);
}
if (this.props.data.issues.length === 0) {
return (
<div>
<div className="notification">
Awesome! No issues found!
</div>
<Stats data={ this.props.data } />
</div>
);
}
var issues = this.props.data.issues
.filter(function(issue) {
return this.props.severity.includes(issue.severity);
}.bind(this))
.filter(function(issue) {
return this.props.confidence.includes(issue.confidence);
}.bind(this))
.filter(function(issue) {
if (this.props.issueType) {
return issue.details.toLowerCase().startsWith(this.props.issueType.toLowerCase());
} else {
return true
}
}.bind(this))
.map(function(issue) {
return (<Issue data={issue} />);
}.bind(this));
if (issues.length === 0) {
return (
<div>
<div className="notification">
No issues matched given filters
(of total { this.props.data.issues.length } issues).
</div>
<Stats data={ this.props.data } />
</div>
);
}
return (
<div className="issues">
{ issues }
<Stats data={ this.props.data } />
</div>
);
}
});
var LevelSelector = React.createClass({
handleChange: function(level) {
return function(e) {
var updated = this.props.selected
.filter(function(item) { return item != level; });
if (e.target.checked) {
updated.push(level);
}
this.props.onChange(updated);
}.bind(this);
},
render: function() {
var highDisabled = !this.props.available.includes("HIGH");
var mediumDisabled = !this.props.available.includes("MEDIUM");
var lowDisabled = !this.props.available.includes("LOW");
return (
<span>
<label className={"label checkbox " + (highDisabled ? "disabled" : "") }>
<input
type="checkbox"
checked={ this.props.selected.includes("HIGH") }
disabled={ highDisabled }
onChange={ this.handleChange("HIGH") }/>
High
</label>
<label className={"label checkbox " + (mediumDisabled ? "disabled" : "") }>
<input
type="checkbox"
checked={ this.props.selected.includes("MEDIUM") }
disabled={ mediumDisabled }
onChange={ this.handleChange("MEDIUM") }/>
Medium
</label>
<label className={"label checkbox " + (lowDisabled ? "disabled" : "") }>
<input
type="checkbox"
checked={ this.props.selected.includes("LOW") }
disabled={ lowDisabled }
onChange={ this.handleChange("LOW") }/>
Low
</label>
</span>
);
}
});
var Navigation = React.createClass({
updateSeverity: function(vals) {
this.props.onSeverity(vals);
},
updateConfidence: function(vals) {
this.props.onConfidence(vals);
},
updateIssueType: function(e) {
if (e.target.value == "all") {
this.props.onIssueType(null);
} else {
this.props.onIssueType(e.target.value);
}
},
render: function() {
var issueTypes = this.props.allIssueTypes
.map(function(it) {
return (
<option value={ it } selected={ this.props.issueType == it }>
{ it }
</option>
);
}.bind(this));
return (
<nav className="panel">
<div className="panel-heading">
Filters
</div>
<div className="panel-block">
<strong>
Severity
</strong>
</div>
<div className="panel-block">
<LevelSelector
selected={ this.props.severity }
available={ this.props.allSeverities }
onChange={ this.updateSeverity } />
</div>
<div className="panel-block">
<strong>
Confidence
</strong>
</div>
<div className="panel-block">
<LevelSelector
selected={ this.props.confidence }
available={ this.props.allConfidences }
onChange={ this.updateConfidence } />
</div>
<div className="panel-block">
<strong>
Issue Type
</strong>
</div>
<div className="panel-block">
<select onChange={ this.updateIssueType }>
<option value="all" selected={ !this.props.issueType }>
(all)
</option>
{ issueTypes }
</select>
</div>
</nav>
);
}
});
var IssueBrowser = React.createClass({
getInitialState: function() {
return {};
},
componentWillMount: function() {
this.updateIssues(this.props.data);
},
handleSeverity: function(val) {
this.updateIssueTypes(this.props.data.issues, val, this.state.confidence);
this.setState({severity: val});
},
handleConfidence: function(val) {
this.updateIssueTypes(this.props.data.issues, this.state.severity, val);
this.setState({confidence: val});
},
handleIssueType: function(val) {
this.setState({issueType: val});
},
updateIssues: function(data) {
if (!data) {
this.setState({data: data});
return;
}
var allSeverities = data.issues
.map(function(issue) {
return issue.severity
})
.sort()
.filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
});
var allConfidences = data.issues
.map(function(issue) {
return issue.confidence
})
.sort()
.filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
});
var selectedSeverities = allSeverities;
var selectedConfidences = allConfidences;
this.updateIssueTypes(data.issues, selectedSeverities, selectedConfidences);
this.setState({
data: data,
severity: selectedSeverities,
allSeverities: allSeverities,
confidence: selectedConfidences,
allConfidences: allConfidences,
issueType: null
});
},
updateIssueTypes: function(issues, severities, confidences) {
var allTypes = issues
.filter(function(issue) {
return severities.includes(issue.severity);
})
.filter(function(issue) {
return confidences.includes(issue.confidence);
})
.map(function(issue) {
return issue.details;
})
.sort()
.filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
});
if (this.state.issueType && !allTypes.includes(this.state.issueType)) {
this.setState({issueType: null});
}
this.setState({allIssueTypes: allTypes});
},
render: function() {
return (
<div className="content">
<div className="columns">
<div className="column is-one-quarter">
<Navigation
severity={ this.state.severity }
confidence={ this.state.confidence }
issueType={ this.state.issueType }
allSeverities={ this.state.allSeverities }
allConfidences={ this.state.allConfidences }
allIssueTypes={ this.state.allIssueTypes }
onSeverity={ this.handleSeverity }
onConfidence={ this.handleConfidence }
onIssueType={ this.handleIssueType }
/>
</div>
<div className="column is-three-quarters">
<Issues
data={ this.props.data }
severity={ this.state.severity }
confidence={ this.state.confidence }
issueType={ this.state.issueType }
/>
</div>
</div>
</div>
);
}
});
ReactDOM.render(
<IssueBrowser data={ data } />,
document.getElementById("content")
);
</script>
</body>
</html>`

View File

@ -1,82 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import "go/ast"
func resolveIdent(n *ast.Ident, c *Context) bool {
if n.Obj == nil || n.Obj.Kind != ast.Var {
return true
}
if node, ok := n.Obj.Decl.(ast.Node); ok {
return TryResolve(node, c)
}
return false
}
func resolveAssign(n *ast.AssignStmt, c *Context) bool {
for _, arg := range n.Rhs {
if !TryResolve(arg, c) {
return false
}
}
return true
}
func resolveCompLit(n *ast.CompositeLit, c *Context) bool {
for _, arg := range n.Elts {
if !TryResolve(arg, c) {
return false
}
}
return true
}
func resolveBinExpr(n *ast.BinaryExpr, c *Context) bool {
return (TryResolve(n.X, c) && TryResolve(n.Y, c))
}
func resolveCallExpr(n *ast.CallExpr, c *Context) bool {
// TODO(tkelsey): next step, full function resolution
return false
}
// TryResolve will attempt, given a subtree starting at some ATS node, to resolve
// all values contained within to a known constant. It is used to check for any
// unkown values in compound expressions.
func TryResolve(n ast.Node, c *Context) bool {
switch node := n.(type) {
case *ast.BasicLit:
return true
case *ast.CompositeLit:
return resolveCompLit(node, c)
case *ast.Ident:
return resolveIdent(node, c)
case *ast.AssignStmt:
return resolveAssign(node, c)
case *ast.CallExpr:
return resolveCallExpr(node, c)
case *ast.BinaryExpr:
return resolveBinExpr(node, c)
}
return false
}

View File

@ -1,58 +0,0 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gas
import (
"go/ast"
"reflect"
)
// The Rule interface used by all rules supported by GAS.
type Rule interface {
Match(ast.Node, *Context) (*Issue, error)
}
// RuleBuilder is used to register a rule definition with the analyzer
type RuleBuilder func(c Config) (Rule, []ast.Node)
// A RuleSet maps lists of rules to the type of AST node they should be run on.
// The anaylzer will only invoke rules contained in the list associated with the
// type of AST node it is currently visiting.
type RuleSet map[reflect.Type][]Rule
// NewRuleSet constructs a new RuleSet
func NewRuleSet() RuleSet {
return make(RuleSet)
}
// Register adds a trigger for the supplied rule for the the
// specified ast nodes.
func (r RuleSet) Register(rule Rule, nodes ...ast.Node) {
for _, n := range nodes {
t := reflect.TypeOf(n)
if rules, ok := r[t]; ok {
r[t] = append(rules, rule)
} else {
r[t] = []Rule{rule}
}
}
}
// RegisteredFor will return all rules that are registered for a
// specified ast node.
func (r RuleSet) RegisteredFor(n ast.Node) []Rule {
if rules, found := r[reflect.TypeOf(n)]; found {
return rules
}
return []Rule{}
}

View File

@ -1,47 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type usingBigExp struct {
gas.MetaData
pkg string
calls []string
}
func (r *usingBigExp) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matched := gas.MatchCallByType(n, c, r.pkg, r.calls...); matched {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
}
return nil, nil
}
// NewUsingBigExp detects issues with modulus == 0 for Bignum
func NewUsingBigExp(conf gas.Config) (gas.Rule, []ast.Node) {
return &usingBigExp{
pkg: "*math/big.Int",
calls: []string{"Exp"},
MetaData: gas.MetaData{
What: "Use of math/big.Int.Exp function should be audited for modulus == 0",
Severity: gas.Low,
Confidence: gas.High,
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,59 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"regexp"
"github.com/GoASTScanner/gas"
)
// Looks for net.Listen("0.0.0.0") or net.Listen(":8080")
type bindsToAllNetworkInterfaces struct {
gas.MetaData
calls gas.CallList
pattern *regexp.Regexp
}
func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
callExpr := r.calls.ContainsCallExpr(n, c)
if callExpr == nil {
return nil, nil
}
if arg, err := gas.GetString(callExpr.Args[1]); err == nil {
if r.pattern.MatchString(arg) {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
}
}
return nil, nil
}
// NewBindsToAllNetworkInterfaces detects socket connections that are setup to
// listen on all network interfaces.
func NewBindsToAllNetworkInterfaces(conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList()
calls.Add("net", "Listen")
calls.Add("crypto/tls", "Listen")
return &bindsToAllNetworkInterfaces{
calls: calls,
pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`),
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
What: "Binds to all network interfaces",
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,82 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"strings"
"github.com/GoASTScanner/gas"
)
type blacklistedImport struct {
gas.MetaData
Blacklisted map[string]string
}
func unquote(original string) string {
copy := strings.TrimSpace(original)
copy = strings.TrimLeft(copy, `"`)
return strings.TrimRight(copy, `"`)
}
func (r *blacklistedImport) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node, ok := n.(*ast.ImportSpec); ok {
if description, ok := r.Blacklisted[unquote(node.Path.Value)]; ok {
return gas.NewIssue(c, node, description, r.Severity, r.Confidence), nil
}
}
return nil, nil
}
// NewBlacklistedImports reports when a blacklisted import is being used.
// Typically when a deprecated technology is being used.
func NewBlacklistedImports(conf gas.Config, blacklist map[string]string) (gas.Rule, []ast.Node) {
return &blacklistedImport{
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
},
Blacklisted: blacklist,
}, []ast.Node{(*ast.ImportSpec)(nil)}
}
// NewBlacklistedImportMD5 fails if MD5 is imported
func NewBlacklistedImportMD5(conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(conf, map[string]string{
"crypto/md5": "Blacklisted import crypto/md5: weak cryptographic primitive",
})
}
// NewBlacklistedImportDES fails if DES is imported
func NewBlacklistedImportDES(conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(conf, map[string]string{
"crypto/des": "Blacklisted import crypto/des: weak cryptographic primitive",
})
}
// NewBlacklistedImportRC4 fails if DES is imported
func NewBlacklistedImportRC4(conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(conf, map[string]string{
"crypto/rc4": "Blacklisted import crypto/rc4: weak cryptographic primitive",
})
}
// NewBlacklistedImportCGI fails if CGI is imported
func NewBlacklistedImportCGI(conf gas.Config) (gas.Rule, []ast.Node) {
return NewBlacklistedImports(conf, map[string]string{
"net/http/cgi": "Blacklisted import net/http/cgi: Go versions < 1.6.3 are vulnerable to Httpoxy attack: (CVE-2016-5386)",
})
}

View File

@ -1,98 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"go/types"
"github.com/GoASTScanner/gas"
)
type noErrorCheck struct {
gas.MetaData
whitelist gas.CallList
}
func returnsError(callExpr *ast.CallExpr, ctx *gas.Context) int {
if tv := ctx.Info.TypeOf(callExpr); tv != nil {
switch t := tv.(type) {
case *types.Tuple:
for pos := 0; pos < t.Len(); pos++ {
variable := t.At(pos)
if variable != nil && variable.Type().String() == "error" {
return pos
}
}
case *types.Named:
if t.String() == "error" {
return 0
}
}
}
return -1
}
func (r *noErrorCheck) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
switch stmt := n.(type) {
case *ast.AssignStmt:
for _, expr := range stmt.Rhs {
if callExpr, ok := expr.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(expr, ctx) == nil {
pos := returnsError(callExpr, ctx)
if pos < 0 || pos >= len(stmt.Lhs) {
return nil, nil
}
if id, ok := stmt.Lhs[pos].(*ast.Ident); ok && id.Name == "_" {
return gas.NewIssue(ctx, n, r.What, r.Severity, r.Confidence), nil
}
}
}
case *ast.ExprStmt:
if callExpr, ok := stmt.X.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(stmt.X, ctx) == nil {
pos := returnsError(callExpr, ctx)
if pos >= 0 {
return gas.NewIssue(ctx, n, r.What, r.Severity, r.Confidence), nil
}
}
}
return nil, nil
}
// NewNoErrorCheck detects if the returned error is unchecked
func NewNoErrorCheck(conf gas.Config) (gas.Rule, []ast.Node) {
// TODO(gm) Come up with sensible defaults here. Or flip it to use a
// black list instead.
whitelist := gas.NewCallList()
whitelist.AddAll("bytes.Buffer", "Write", "WriteByte", "WriteRune", "WriteString")
whitelist.AddAll("fmt", "Print", "Printf", "Println", "Fprint", "Fprintf", "Fprintln")
whitelist.Add("io.PipeWriter", "CloseWithError")
if configured, ok := conf["G104"]; ok {
if whitelisted, ok := configured.(map[string][]string); ok {
for key, val := range whitelisted {
whitelist.AddAll(key, val...)
}
}
}
return &noErrorCheck{
MetaData: gas.MetaData{
Severity: gas.Low,
Confidence: gas.High,
What: "Errors unhandled.",
},
whitelist: whitelist,
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.ExprStmt)(nil)}
}

View File

@ -1,89 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"fmt"
"go/ast"
"strconv"
"github.com/GoASTScanner/gas"
)
type filePermissions struct {
gas.MetaData
mode int64
pkg string
calls []string
}
func getConfiguredMode(conf map[string]interface{}, configKey string, defaultMode int64) int64 {
var mode = defaultMode
if value, ok := conf[configKey]; ok {
switch value.(type) {
case int64:
mode = value.(int64)
case string:
if m, e := strconv.ParseInt(value.(string), 0, 64); e != nil {
mode = defaultMode
} else {
mode = m
}
}
}
return mode
}
func (r *filePermissions) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if callexpr, matched := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matched {
modeArg := callexpr.Args[len(callexpr.Args)-1]
if mode, err := gas.GetInt(modeArg); err == nil && mode > r.mode {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
}
}
return nil, nil
}
// NewFilePerms creates a rule to detect file creation with a more permissive than configured
// permission mask.
func NewFilePerms(conf gas.Config) (gas.Rule, []ast.Node) {
mode := getConfiguredMode(conf, "G302", 0600)
return &filePermissions{
mode: mode,
pkg: "os",
calls: []string{"OpenFile", "Chmod"},
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
What: fmt.Sprintf("Expect file permissions to be %#o or less", mode),
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}
// NewMkdirPerms creates a rule to detect directory creation with more permissive than
// configured permission mask.
func NewMkdirPerms(conf gas.Config) (gas.Rule, []ast.Node) {
mode := getConfiguredMode(conf, "G301", 0750)
return &filePermissions{
mode: mode,
pkg: "os",
calls: []string{"Mkdir", "MkdirAll"},
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
What: fmt.Sprintf("Expect directory permissions to be %#o or less", mode),
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,150 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"go/token"
"regexp"
"strconv"
"github.com/GoASTScanner/gas"
"github.com/nbutton23/zxcvbn-go"
)
type credentials struct {
gas.MetaData
pattern *regexp.Regexp
entropyThreshold float64
perCharThreshold float64
truncate int
ignoreEntropy bool
}
func truncate(s string, n int) string {
if n > len(s) {
return s
}
return s[:n]
}
func (r *credentials) isHighEntropyString(str string) bool {
s := truncate(str, r.truncate)
info := zxcvbn.PasswordStrength(s, []string{})
entropyPerChar := info.Entropy / float64(len(s))
return (info.Entropy >= r.entropyThreshold ||
(info.Entropy >= (r.entropyThreshold/2) &&
entropyPerChar >= r.perCharThreshold))
}
func (r *credentials) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
switch node := n.(type) {
case *ast.AssignStmt:
return r.matchAssign(node, ctx)
case *ast.GenDecl:
return r.matchGenDecl(node, ctx)
}
return nil, nil
}
func (r *credentials) matchAssign(assign *ast.AssignStmt, ctx *gas.Context) (*gas.Issue, error) {
for _, i := range assign.Lhs {
if ident, ok := i.(*ast.Ident); ok {
if r.pattern.MatchString(ident.Name) {
for _, e := range assign.Rhs {
if val, err := gas.GetString(e); err == nil {
if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) {
return gas.NewIssue(ctx, assign, r.What, r.Severity, r.Confidence), nil
}
}
}
}
}
}
return nil, nil
}
func (r *credentials) matchGenDecl(decl *ast.GenDecl, ctx *gas.Context) (*gas.Issue, error) {
if decl.Tok != token.CONST && decl.Tok != token.VAR {
return nil, nil
}
for _, spec := range decl.Specs {
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
for index, ident := range valueSpec.Names {
if r.pattern.MatchString(ident.Name) && valueSpec.Values != nil {
// const foo, bar = "same value"
if len(valueSpec.Values) <= index {
index = len(valueSpec.Values) - 1
}
if val, err := gas.GetString(valueSpec.Values[index]); err == nil {
if r.ignoreEntropy || (!r.ignoreEntropy && r.isHighEntropyString(val)) {
return gas.NewIssue(ctx, valueSpec, r.What, r.Severity, r.Confidence), nil
}
}
}
}
}
}
return nil, nil
}
// NewHardcodedCredentials attempts to find high entropy string constants being
// assigned to variables that appear to be related to credentials.
func NewHardcodedCredentials(conf gas.Config) (gas.Rule, []ast.Node) {
pattern := `(?i)passwd|pass|password|pwd|secret|token`
entropyThreshold := 80.0
perCharThreshold := 3.0
ignoreEntropy := false
var truncateString = 16
if val, ok := conf["G101"]; ok {
conf := val.(map[string]string)
if configPattern, ok := conf["pattern"]; ok {
pattern = configPattern
}
if configIgnoreEntropy, ok := conf["ignore_entropy"]; ok {
if parsedBool, err := strconv.ParseBool(configIgnoreEntropy); err == nil {
ignoreEntropy = parsedBool
}
}
if configEntropyThreshold, ok := conf["entropy_threshold"]; ok {
if parsedNum, err := strconv.ParseFloat(configEntropyThreshold, 64); err == nil {
entropyThreshold = parsedNum
}
}
if configCharThreshold, ok := conf["per_char_threshold"]; ok {
if parsedNum, err := strconv.ParseFloat(configCharThreshold, 64); err == nil {
perCharThreshold = parsedNum
}
}
if configTruncate, ok := conf["truncate"]; ok {
if parsedInt, err := strconv.Atoi(configTruncate); err == nil {
truncateString = parsedInt
}
}
}
return &credentials{
pattern: regexp.MustCompile(pattern),
entropyThreshold: entropyThreshold,
perCharThreshold: perCharThreshold,
ignoreEntropy: ignoreEntropy,
truncate: truncateString,
MetaData: gas.MetaData{
What: "Potential hardcoded credentials",
Confidence: gas.Low,
Severity: gas.High,
},
}, []ast.Node{(*ast.AssignStmt)(nil), (*ast.GenDecl)(nil)}
}

View File

@ -1,50 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type weakRand struct {
gas.MetaData
funcNames []string
packagePath string
}
func (w *weakRand) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
for _, funcName := range w.funcNames {
if _, matched := gas.MatchCallByPackage(n, c, w.packagePath, funcName); matched {
return gas.NewIssue(c, n, w.What, w.Severity, w.Confidence), nil
}
}
return nil, nil
}
// NewWeakRandCheck detects the use of random number generator that isn't cryptographically secure
func NewWeakRandCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &weakRand{
funcNames: []string{"Read", "Int"},
packagePath: "math/rand",
MetaData: gas.MetaData{
Severity: gas.High,
Confidence: gas.Medium,
What: "Use of weak random number generator (math/rand instead of crypto/rand)",
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,53 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"fmt"
"go/ast"
"github.com/GoASTScanner/gas"
)
type weakKeyStrength struct {
gas.MetaData
calls gas.CallList
bits int
}
func (w *weakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if callExpr := w.calls.ContainsCallExpr(n, c); callExpr != nil {
if bits, err := gas.GetInt(callExpr.Args[1]); err == nil && bits < (int64)(w.bits) {
return gas.NewIssue(c, n, w.What, w.Severity, w.Confidence), nil
}
}
return nil, nil
}
// NewWeakKeyStrength builds a rule that detects RSA keys < 2048 bits
func NewWeakKeyStrength(conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList()
calls.Add("crypto/rsa", "GenerateKey")
bits := 2048
return &weakKeyStrength{
calls: calls,
bits: bits,
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
What: fmt.Sprintf("RSA keys should be at least %d bits", bits),
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,102 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"github.com/GoASTScanner/gas"
)
// RuleDefinition contains the description of a rule and a mechanism to
// create it.
type RuleDefinition struct {
Description string
Create gas.RuleBuilder
}
// RuleList is a mapping of rule ID's to rule definitions
type RuleList map[string]RuleDefinition
// Builders returns all the create methods for a given rule list
func (rl RuleList) Builders() []gas.RuleBuilder {
builders := make([]gas.RuleBuilder, 0, len(rl))
for _, def := range rl {
builders = append(builders, def.Create)
}
return builders
}
// RuleFilter can be used to include or exclude a rule depending on the return
// value of the function
type RuleFilter func(string) bool
// NewRuleFilter is a closure that will include/exclude the rule ID's based on
// the supplied boolean value.
func NewRuleFilter(action bool, ruleIDs ...string) RuleFilter {
rulelist := make(map[string]bool)
for _, rule := range ruleIDs {
rulelist[rule] = true
}
return func(rule string) bool {
if _, found := rulelist[rule]; found {
return action
}
return !action
}
}
// Generate the list of rules to use
func Generate(filters ...RuleFilter) RuleList {
rules := map[string]RuleDefinition{
// misc
"G101": {"Look for hardcoded credentials", NewHardcodedCredentials},
"G102": {"Bind to all interfaces", NewBindsToAllNetworkInterfaces},
"G103": {"Audit the use of unsafe block", NewUsingUnsafe},
"G104": {"Audit errors not checked", NewNoErrorCheck},
"G105": {"Audit the use of big.Exp function", NewUsingBigExp},
"G106": {"Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey},
// injection
"G201": {"SQL query construction using format string", NewSQLStrFormat},
"G202": {"SQL query construction using string concatenation", NewSQLStrConcat},
"G203": {"Use of unescaped data in HTML templates", NewTemplateCheck},
"G204": {"Audit use of command execution", NewSubproc},
// filesystem
"G301": {"Poor file permissions used when creating a directory", NewMkdirPerms},
"G302": {"Poor file permisions used when creation file or using chmod", NewFilePerms},
"G303": {"Creating tempfile using a predictable path", NewBadTempFile},
// crypto
"G401": {"Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography},
"G402": {"Look for bad TLS connection settings", NewIntermediateTLSCheck},
"G403": {"Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength},
"G404": {"Insecure random number source (rand)", NewWeakRandCheck},
// blacklist
"G501": {"Import blacklist: crypto/md5", NewBlacklistedImportMD5},
"G502": {"Import blacklist: crypto/des", NewBlacklistedImportDES},
"G503": {"Import blacklist: crypto/rc4", NewBlacklistedImportRC4},
"G504": {"Import blacklist: net/http/cgi", NewBlacklistedImportCGI},
}
for rule := range rules {
for _, filter := range filters {
if filter(rule) {
delete(rules, rule)
}
}
}
return rules
}

View File

@ -1,125 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"regexp"
"github.com/GoASTScanner/gas"
)
type sqlStatement struct {
gas.MetaData
// Contains a list of patterns which must all match for the rule to match.
patterns []*regexp.Regexp
}
// See if the string matches the patterns for the statement.
func (s sqlStatement) MatchPatterns(str string) bool {
for _, pattern := range s.patterns {
if !pattern.MatchString(str) {
return false
}
}
return true
}
type sqlStrConcat struct {
sqlStatement
}
// see if we can figure out what it is
func (s *sqlStrConcat) checkObject(n *ast.Ident) bool {
if n.Obj != nil {
return n.Obj.Kind != ast.Var && n.Obj.Kind != ast.Fun
}
return false
}
// Look for "SELECT * FROM table WHERE " + " ' OR 1=1"
func (s *sqlStrConcat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node, ok := n.(*ast.BinaryExpr); ok {
if start, ok := node.X.(*ast.BasicLit); ok {
if str, e := gas.GetString(start); e == nil {
if !s.MatchPatterns(str) {
return nil, nil
}
if _, ok := node.Y.(*ast.BasicLit); ok {
return nil, nil // string cat OK
}
if second, ok := node.Y.(*ast.Ident); ok && s.checkObject(second) {
return nil, nil
}
return gas.NewIssue(c, n, s.What, s.Severity, s.Confidence), nil
}
}
}
return nil, nil
}
// NewSQLStrConcat looks for cases where we are building SQL strings via concatenation
func NewSQLStrConcat(conf gas.Config) (gas.Rule, []ast.Node) {
return &sqlStrConcat{
sqlStatement: sqlStatement{
patterns: []*regexp.Regexp{
regexp.MustCompile(`(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) `),
},
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
What: "SQL string concatenation",
},
},
}, []ast.Node{(*ast.BinaryExpr)(nil)}
}
type sqlStrFormat struct {
sqlStatement
calls gas.CallList
}
// Looks for "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)"
func (s *sqlStrFormat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
// TODO(gm) improve confidence if database/sql is being used
if node := s.calls.ContainsCallExpr(n, c); node != nil {
if arg, e := gas.GetString(node.Args[0]); s.MatchPatterns(arg) && e == nil {
return gas.NewIssue(c, n, s.What, s.Severity, s.Confidence), nil
}
}
return nil, nil
}
// NewSQLStrFormat looks for cases where we're building SQL query strings using format strings
func NewSQLStrFormat(conf gas.Config) (gas.Rule, []ast.Node) {
rule := &sqlStrFormat{
calls: gas.NewCallList(),
sqlStatement: sqlStatement{
patterns: []*regexp.Regexp{
regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "),
regexp.MustCompile("%[^bdoxXfFp]"),
},
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
What: "SQL string formatting",
},
},
}
rule.calls.AddAll("fmt", "Sprint", "Sprintf", "Sprintln")
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,33 +0,0 @@
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type sshHostKey struct {
gas.MetaData
pkg string
calls []string
}
func (r *sshHostKey) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
}
return nil, nil
}
// NewSSHHostKey rule detects the use of insecure ssh HostKeyCallback.
func NewSSHHostKey(conf gas.Config) (gas.Rule, []ast.Node) {
return &sshHostKey{
pkg: "golang.org/x/crypto/ssh",
calls: []string{"InsecureIgnoreHostKey"},
MetaData: gas.MetaData{
What: "Use of ssh InsecureIgnoreHostKey should be audited",
Severity: gas.Medium,
Confidence: gas.High,
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,58 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"go/types"
"github.com/GoASTScanner/gas"
)
type subprocess struct {
gas.CallList
}
// TODO(gm) The only real potential for command injection with a Go project
// is something like this:
//
// syscall.Exec("/bin/sh", []string{"-c", tainted})
//
// E.g. Input is correctly escaped but the execution context being used
// is unsafe. For example:
//
// syscall.Exec("echo", "foobar" + tainted)
func (r *subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node := r.ContainsCallExpr(n, c); node != nil {
for _, arg := range node.Args {
if ident, ok := arg.(*ast.Ident); ok {
obj := c.Info.ObjectOf(ident)
if _, ok := obj.(*types.Var); ok && !gas.TryResolve(ident, c) {
return gas.NewIssue(c, n, "Subprocess launched with variable", gas.Medium, gas.High), nil
}
}
}
return gas.NewIssue(c, n, "Subprocess launching should be audited", gas.Low, gas.High), nil
}
return nil, nil
}
// NewSubproc detects cases where we are forking out to an external process
func NewSubproc(conf gas.Config) (gas.Rule, []ast.Node) {
rule := &subprocess{gas.NewCallList()}
rule.Add("os/exec", "Command")
rule.Add("syscall", "Exec")
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,53 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"regexp"
"github.com/GoASTScanner/gas"
)
type badTempFile struct {
gas.MetaData
calls gas.CallList
args *regexp.Regexp
}
func (t *badTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if node := t.calls.ContainsCallExpr(n, c); node != nil {
if arg, e := gas.GetString(node.Args[0]); t.args.MatchString(arg) && e == nil {
return gas.NewIssue(c, n, t.What, t.Severity, t.Confidence), nil
}
}
return nil, nil
}
// NewBadTempFile detects direct writes to predictable path in temporary directory
func NewBadTempFile(conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList()
calls.Add("io/ioutil", "WriteFile")
calls.Add("os", "Create")
return &badTempFile{
calls: calls,
args: regexp.MustCompile(`^/tmp/.*$|^/var/tmp/.*$`),
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
What: "File creation in shared tmp directory without using ioutil.Tempfile",
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,56 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type templateCheck struct {
gas.MetaData
calls gas.CallList
}
func (t *templateCheck) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if node := t.calls.ContainsCallExpr(n, c); node != nil {
for _, arg := range node.Args {
if _, ok := arg.(*ast.BasicLit); !ok { // basic lits are safe
return gas.NewIssue(c, n, t.What, t.Severity, t.Confidence), nil
}
}
}
return nil, nil
}
// NewTemplateCheck constructs the template check rule. This rule is used to
// find use of tempaltes where HTML/JS escaping is not being used
func NewTemplateCheck(conf gas.Config) (gas.Rule, []ast.Node) {
calls := gas.NewCallList()
calls.Add("html/template", "HTML")
calls.Add("html/template", "HTMLAttr")
calls.Add("html/template", "JS")
calls.Add("html/template", "URL")
return &templateCheck{
calls: calls,
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.Low,
What: "this method will not auto-escape HTML. Verify data is well formed.",
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,125 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:generate tlsconfig
package rules
import (
"fmt"
"go/ast"
"github.com/GoASTScanner/gas"
)
type insecureConfigTLS struct {
MinVersion int16
MaxVersion int16
requiredType string
goodCiphers []string
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
func (t *insecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gas.Context) *gas.Issue {
if ciphers, ok := n.(*ast.CompositeLit); ok {
for _, cipher := range ciphers.Elts {
if ident, ok := cipher.(*ast.SelectorExpr); ok {
if !stringInSlice(ident.Sel.Name, t.goodCiphers) {
err := fmt.Sprintf("TLS Bad Cipher Suite: %s", ident.Sel.Name)
return gas.NewIssue(c, ident, err, gas.High, gas.High)
}
}
}
}
return nil
}
func (t *insecureConfigTLS) processTLSConfVal(n *ast.KeyValueExpr, c *gas.Context) *gas.Issue {
if ident, ok := n.Key.(*ast.Ident); ok {
switch ident.Name {
case "InsecureSkipVerify":
if node, ok := n.Value.(*ast.Ident); ok {
if node.Name != "false" {
return gas.NewIssue(c, n, "TLS InsecureSkipVerify set true.", gas.High, gas.High)
}
} else {
// TODO(tk): symbol tab look up to get the actual value
return gas.NewIssue(c, n, "TLS InsecureSkipVerify may be true.", gas.High, gas.Low)
}
case "PreferServerCipherSuites":
if node, ok := n.Value.(*ast.Ident); ok {
if node.Name == "false" {
return gas.NewIssue(c, n, "TLS PreferServerCipherSuites set false.", gas.Medium, gas.High)
}
} else {
// TODO(tk): symbol tab look up to get the actual value
return gas.NewIssue(c, n, "TLS PreferServerCipherSuites may be false.", gas.Medium, gas.Low)
}
case "MinVersion":
if ival, ierr := gas.GetInt(n.Value); ierr == nil {
if (int16)(ival) < t.MinVersion {
return gas.NewIssue(c, n, "TLS MinVersion too low.", gas.High, gas.High)
}
// TODO(tk): symbol tab look up to get the actual value
return gas.NewIssue(c, n, "TLS MinVersion may be too low.", gas.High, gas.Low)
}
case "MaxVersion":
if ival, ierr := gas.GetInt(n.Value); ierr == nil {
if (int16)(ival) < t.MaxVersion {
return gas.NewIssue(c, n, "TLS MaxVersion too low.", gas.High, gas.High)
}
// TODO(tk): symbol tab look up to get the actual value
return gas.NewIssue(c, n, "TLS MaxVersion may be too low.", gas.High, gas.Low)
}
case "CipherSuites":
if ret := t.processTLSCipherSuites(n.Value, c); ret != nil {
return ret
}
}
}
return nil
}
func (t *insecureConfigTLS) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if complit, ok := n.(*ast.CompositeLit); ok && complit.Type != nil {
actualType := c.Info.TypeOf(complit.Type)
if actualType != nil && actualType.String() == t.requiredType {
for _, elt := range complit.Elts {
if kve, ok := elt.(*ast.KeyValueExpr); ok {
issue := t.processTLSConfVal(kve, c)
if issue != nil {
return issue, nil
}
}
}
}
}
return nil, nil
}

View File

@ -1,132 +0,0 @@
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
// NewModernTLSCheck creates a check for Modern TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool
func NewModernTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: 0x0303,
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}
// NewIntermediateTLSCheck creates a check for Intermediate TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool
func NewIntermediateTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: 0x0301,
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}
// NewOldTLSCheck creates a check for Old TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool
func NewOldTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: 0x0300,
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA",
"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA",
"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA",
"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DHE_RSA_WITH_SEED_CBC_SHA",
"TLS_DHE_DSS_WITH_SEED_CBC_SHA",
"TLS_RSA_WITH_SEED_CBC_SHA",
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}

View File

@ -1,48 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type usingUnsafe struct {
gas.MetaData
pkg string
calls []string
}
func (r *usingUnsafe) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
}
return nil, nil
}
// NewUsingUnsafe rule detects the use of the unsafe package. This is only
// really useful for auditing purposes.
func NewUsingUnsafe(conf gas.Config) (gas.Rule, []ast.Node) {
return &usingUnsafe{
pkg: "unsafe",
calls: []string{"Alignof", "Offsetof", "Sizeof", "Pointer"},
MetaData: gas.MetaData{
What: "Use of unsafe calls should be audited",
Severity: gas.Low,
Confidence: gas.High,
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,53 +0,0 @@
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type usesWeakCryptography struct {
gas.MetaData
blacklist map[string][]string
}
func (r *usesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
for pkg, funcs := range r.blacklist {
if _, matched := gas.MatchCallByPackage(n, c, pkg, funcs...); matched {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
}
}
return nil, nil
}
// NewUsesWeakCryptography detects uses of des.* md5.* or rc4.*
func NewUsesWeakCryptography(conf gas.Config) (gas.Rule, []ast.Node) {
calls := make(map[string][]string)
calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"}
calls["crypto/md5"] = []string{"New", "Sum"}
calls["crypto/rc4"] = []string{"NewCipher"}
rule := &usesWeakCryptography{
blacklist: calls,
MetaData: gas.MetaData{
Severity: gas.Medium,
Confidence: gas.High,
What: "Use of weak cryptographic primitive",
},
}
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@ -1,27 +0,0 @@
Copyright (c) 2013 Frederik Zipp. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright owner nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,31 +0,0 @@
Gocyclo calculates cyclomatic complexities of functions in Go source code.
The cyclomatic complexity of a function is calculated according to the
following rules:
1 is the base complexity of a function
+1 for each 'if', 'for', 'case', '&&' or '||'
To install, run
$ go get github.com/fzipp/gocyclo
and put the resulting binary in one of your PATH directories if
`$GOPATH/bin` isn't already in your PATH.
Usage:
$ gocyclo [<flag> ...] <Go file or directory> ...
Examples:
$ gocyclo .
$ gocyclo main.go
$ gocyclo -top 10 src/
$ gocyclo -over 25 docker
$ gocyclo -avg .
The output fields for each line are:
<complexity> <package> <function> <file:row:column>

View File

@ -1,222 +0,0 @@
// Copyright 2013 Frederik Zipp. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Gocyclo calculates the cyclomatic complexities of functions and
// methods in Go source code.
//
// Usage:
// gocyclo [<flag> ...] <Go file or directory> ...
//
// Flags
// -over N show functions with complexity > N only and
// return exit code 1 if the output is non-empty
// -top N show the top N most complex functions only
// -avg show the average complexity
//
// The output fields for each line are:
// <complexity> <package> <function> <file:row:column>
package main
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io"
"os"
"path/filepath"
"sort"
)
const usageDoc = `Calculate cyclomatic complexities of Go functions.
usage:
gocyclo [<flag> ...] <Go file or directory> ...
Flags
-over N show functions with complexity > N only and
return exit code 1 if the set is non-empty
-top N show the top N most complex functions only
-avg show the average complexity over all functions,
not depending on whether -over or -top are set
The output fields for each line are:
<complexity> <package> <function> <file:row:column>
`
func usage() {
fmt.Fprintf(os.Stderr, usageDoc)
os.Exit(2)
}
var (
over = flag.Int("over", 0, "show functions with complexity > N only")
top = flag.Int("top", -1, "show the top N most complex functions only")
avg = flag.Bool("avg", false, "show the average complexity")
)
func main() {
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) == 0 {
usage()
}
stats := analyze(args)
sort.Sort(byComplexity(stats))
written := writeStats(os.Stdout, stats)
if *avg {
showAverage(stats)
}
if *over > 0 && written > 0 {
os.Exit(1)
}
}
func analyze(paths []string) []stat {
stats := make([]stat, 0)
for _, path := range paths {
if isDir(path) {
stats = analyzeDir(path, stats)
} else {
stats = analyzeFile(path, stats)
}
}
return stats
}
func isDir(filename string) bool {
fi, err := os.Stat(filename)
return err == nil && fi.IsDir()
}
func analyzeFile(fname string, stats []stat) []stat {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, fname, nil, 0)
if err != nil {
exitError(err)
}
return buildStats(f, fset, stats)
}
func analyzeDir(dirname string, stats []stat) []stat {
files, _ := filepath.Glob(filepath.Join(dirname, "*.go"))
for _, file := range files {
stats = analyzeFile(file, stats)
}
return stats
}
func exitError(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
func writeStats(w io.Writer, sortedStats []stat) int {
for i, stat := range sortedStats {
if i == *top {
return i
}
if stat.Complexity <= *over {
return i
}
fmt.Fprintln(w, stat)
}
return len(sortedStats)
}
func showAverage(stats []stat) {
fmt.Printf("Average: %.3g\n", average(stats))
}
func average(stats []stat) float64 {
total := 0
for _, s := range stats {
total += s.Complexity
}
return float64(total) / float64(len(stats))
}
type stat struct {
PkgName string
FuncName string
Complexity int
Pos token.Position
}