pax_global_header00006660000000000000000000000064141010303670014505gustar00rootroot0000000000000052 comment=dc97d99c35488e59727a3da089f764df901ba56f labkit-v1.7.0/000077500000000000000000000000001410103036700131465ustar00rootroot00000000000000labkit-v1.7.0/.codeclimate.yml000066400000000000000000000001751410103036700162230ustar00rootroot00000000000000plugins: govet: enabled: true golint: enabled: true gofmt: enabled: true markdownlint: enabled: true labkit-v1.7.0/.gitignore000066400000000000000000000001161410103036700151340ustar00rootroot00000000000000gl-sast-report.json gl-code-quality-report.json *.so .env router node_modules labkit-v1.7.0/.gitlab-ci.yml000066400000000000000000000047251410103036700156120ustar00rootroot00000000000000image: golang:1.16 include: - template: Security/SAST.gitlab-ci.yml - template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml' variables: REPO_NAME: gitlab.com/gitlab-org/labkit GO_SEMANTIC_RELEASE_VERSION: 2.12.0 # Note, GOLANGCI_LINT_VERSION should match .tool-versions! GOLANGCI_LINT_VERSION: 1.41.1 stages: - build - verify - release build_1.14: stage: build image: golang:1.14 script: - ./compile.sh build_1.15: stage: build image: golang:1.15 script: - ./compile.sh build_1.16: stage: build image: golang:1.16 script: - ./compile.sh test_1.14: stage: verify image: golang:1.14 script: - ./test.sh test_1.15: stage: verify image: golang:1.15 script: - ./test.sh test_1.16: stage: verify image: golang:1.16 script: - ./test.sh # The verify step should always use the same version of Go as devs are # likely to be developing with to avoid issues with changes in these tools # between go versions. Since these are simply linter warnings and not # compiler issues, we only need a single version sast: stage: verify # Ensure that all the changes are backwards compatible with GitLab Workhorse backwards_compat_gitaly: stage: verify image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7-golang-1.15-git-2.29 script: - ./backwords_compat.sh https://gitlab.com/gitlab-org/gitaly.git # Ensure that all the changes are backwards compatible with GitLab Container Registry backwards_compat_container_registry: stage: verify script: - ./backwords_compat.sh https://gitlab.com/gitlab-org/container-registry.git mod tidy: stage: verify script: - ./tidy.sh lint: image: name: golangci/golangci-lint:v${GOLANGCI_LINT_VERSION}-alpine entrypoint: ["/bin/sh", "-c"] # git must be installed for golangci-lint's --new-from-rev flag to work. before_script: - apk add --no-cache git bash jq stage: verify script: - ./lint.sh artifacts: reports: codequality: gl-code-quality-report.json paths: - gl-code-quality-report.json commitlint: stage: verify image: node:14-alpine3.12 before_script: - apk add --no-cache git - npm install script: - npx commitlint --from ${CI_MERGE_REQUEST_DIFF_BASE_SHA} --to HEAD --verbose rules: - if: $CI_MERGE_REQUEST_IID release: image: node:14 stage: release rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH when: manual script: - npm install - $(npm bin)/semantic-release labkit-v1.7.0/.golangci.yml000066400000000000000000000023731410103036700155370ustar00rootroot00000000000000run: concurrency: 8 deadline: 1m issues-exit-code: 1 modules-download-mode: readonly tests: true skip-dirs: - vendor build-tags: - continuous_profiler_stackdriver - tracer_static - tracer_static_jaeger - tracer_static_lightstep - tracer_static_datadog output: format: colored-line-number print-issued-lines: true print-linter-name: true linters-settings: errcheck: check-type-assertions: false cyclop: maxComplexity: 10 govet: check-shadowing: false goconst: min-len: 3 min-occurrences: 3 lll: line-length: 160 gosec: # To specify a set of rules to explicitly exclude. # Available rules: https://github.com/securego/gosec#available-rules excludes: - G102 linters: enable-all: true disable: - dupl - exhaustivestruct - forcetypeassert - funlen - gochecknoglobals - gofumpt - gomnd - nlreturn - paralleltest - scopelint # archived - testpackage - unparam - wrapcheck - wsl issues: exclude-rules: - path: _test\.go linters: - bodyclose - buildir - errcheck - goerr113 - noctx - staticcheck - typecheck - wsl labkit-v1.7.0/.mdlrc000066400000000000000000000000201410103036700142400ustar00rootroot00000000000000style "relaxed" labkit-v1.7.0/.tool-versions000066400000000000000000000001511410103036700157670ustar00rootroot00000000000000# asdf plugin add golangci-lint https://github.com/hypnoglow/asdf-golangci-lint.git golangci-lint 1.41.1 labkit-v1.7.0/LICENSE000066400000000000000000000020731410103036700141550ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016-2017 GitLab B.V. 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. labkit-v1.7.0/README.md000066400000000000000000000121041410103036700144230ustar00rootroot00000000000000# LabKit 🧪 🔬 🥼 🥽 🦠 🧫 🧬 💉 **LabKit** is **minimalist library** to provide functionality for Go services at GitLab. ------------------------------------------------------ ## Packages ### Correlation [![](https://godoc.org/gitlab.com/gitlab-org/labkit/correlation?status.svg)](http://godoc.org/gitlab.com/gitlab-org/labkit/correlation) ```go import ( "gitlab.com/gitlab-org/labkit/correlation" ) ``` ### Logging [![](https://godoc.org/gitlab.com/gitlab-org/labkit/log?status.svg)](http://godoc.org/gitlab.com/gitlab-org/labkit/log) ```go import ( logkit "gitlab.com/gitlab-org/labkit/log" ) ``` ### Masking [![](https://godoc.org/gitlab.com/gitlab-org/labkit/mask?status.svg)](http://godoc.org/gitlab.com/gitlab-org/labkit/mask) ```go import ( "gitlab.com/gitlab-org/labkit/mask" ) ``` ### Metrics [![](https://godoc.org/gitlab.com/gitlab-org/labkit/metrics?status.svg)](http://godoc.org/gitlab.com/gitlab-org/labkit/metrics) ```go import ( "gitlab.com/gitlab-org/labkit/metrics" ) ``` ### Monitoring [![](https://godoc.org/gitlab.com/gitlab-org/labkit/monitoring?status.svg)](http://godoc.org/gitlab.com/gitlab-org/labkit/monitoring) ```go import ( "gitlab.com/gitlab-org/labkit/monitoring" ) ``` ## Developing LabKit Anyone can contribute! ### Architectural guidelines Please be aware of the following architectural guidelines. ### Public APIs in LabKit should be stable * LabKit is a library, not an application, and as such, we value compatibility highly. * Public APIs in LabKit should be backward compatible. This should be done by making APIs forward extensible. * Add methods, but do not add (non-optional) parameters to existing methods. * Use [Functional Options](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) where possible. ### APIs should be intuitive and friendly * APIs should have an intuitive interface. * Spend time ensuring thinking about what the best interface is. * Include excellent godocs, including package documentation in `doc.go`. ### Dependency Management * Be extremely careful about the dependencies your packages include. Be prepared to justify any dependencies you bring into a package. * If not users will require a dependency, consider including it in a subpackage (e.g. `gitlab.com/gitlab-org/labkit/correlation/grpc`), especially if the dependency is large or has a deep dependency tree of it's own. * Be even more careful about the dependencies that you expose through the API of a package. * Follow this rule: packages can depend on their parent packages, but not their siblings or children. * If required, declare common models in the root package. ### Architectural Philosophy Taken from [A Philosophy of Software Design, by John Ousterhout](https://www.amazon.com/Philosophy-Software-Design-John-Ousterhout/dp/1732102201). This book is recommended reading. * Modules should be deep. * Interfaces should be designed to make the most common usage as simple as possible. * Separate general-purpose and special-purpose code. * Design it twice. * Comments should describe things that are not obvious from the code. ### Review Process Please assign your MR to a reviewer for a first review, followed by a maintainer review. Currently, the reviewers are: 1. [@igorwwwwwwwwwwwwwwwwwwww](https://gitlab.com/igorwwwwwwwwwwwwwwwwwwww) 1. [@jdrpereira](https://gitlab.com/jdrpereira) 1. [@8bitlife](https://gitlab.com/8bitlife) 1. [@reprazent](https://gitlab.com/reprazent) The maintainers are: 1. [@andrewn](https://gitlab.com/andrewn) 1. [@hphilipps](https://gitlab.com/hphilipps) 1. [@steveazz](https://gitlab.com/steveazz) 1. [@zj-gitlab](https://gitlab.com/zj-gitlab) ### Release Process LabKit uses [semantic releases](https://github.com/semantic-release/semantic-release). Please use the [Conventional Commits](https://www.conventionalcommits.org) commit format. #### When to release a new version A new release should only be created when there is a new user-facing feature or a bug fix. CI, docs, and refactoring shouldn't result in a new release unless it's a big change. ### Downstream Vendoring While not strictly neccessary, it is preferred for the author of changes to also send downstream MRs to the applications that use LabKit. Since the library has a strict backwards compatibility policy, these upgrades *should* be fairly straightforward. Use the `./downstream-vendor.sh` to update the LabKit library in all known GitLab applications. You will need to have `git+ssh` credentials setup to push directly to the repositories. The script currently does not work on forked projects unfortunately. Once completed, the script will output a list of MRs, for your review. ```console $ ./downstream-vendor.sh # ..... #################################### # completed successfully # https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/657 # https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2816 # https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/396 # https://gitlab.com/gitlab-org/container-registry/-/merge_requests/427 # https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/merge_requests/173 ``` labkit-v1.7.0/backwords_compat.sh000077500000000000000000000007361410103036700170350ustar00rootroot00000000000000#!/usr/bin/env bash set -eo pipefail -x repo=$1 cloneDir=$(mktemp -d) CI_COMMIT_SHA=${CI_COMMIT_SHA:-master} git clone "$repo" "$cloneDir" cd "$cloneDir" if [ -n "$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA" ]; then go get gitlab.com/gitlab-org/labkit@"$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA" else go get gitlab.com/gitlab-org/labkit@"$CI_COMMIT_SHA" fi # Ensure go.mod and go.sum are up to date in the cloned repo, otherwise build may fail. go mod tidy make rm -rf "$cloneDir" labkit-v1.7.0/commitlint.config.js000066400000000000000000000016731410103036700171360ustar00rootroot00000000000000module.exports = { parserPreset: 'conventional-changelog-conventionalcommits', rules: { 'body-leading-blank': [1, 'always'], 'body-max-line-length': [2, 'always', 100], 'footer-leading-blank': [1, 'always'], 'footer-max-line-length': [2, 'always', 100], 'header-max-length': [2, 'always', 100], 'scope-case': [2, 'always', 'lower-case'], 'subject-case': [ 2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case'], ], 'subject-empty': [2, 'never'], 'subject-full-stop': [2, 'never', '.'], 'type-case': [2, 'always', 'lower-case'], 'type-empty': [2, 'never'], 'type-enum': [ 2, 'always', [ 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test', ], ], 'scope-enum': [ 2, 'always', [ 'correlation', 'errortracking', 'log', 'mask', 'metrics', 'monitoring', 'tracing', ] ] }, }; labkit-v1.7.0/compile.sh000077500000000000000000000010751410103036700151400ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "${SCRIPT_DIR}" for build_tags in \ "" \ "tracer_static tracer_static_jaeger" \ "tracer_static tracer_static_lightstep" \ "tracer_static tracer_static_datadog" \ "tracer_static tracer_static_stackdriver" \ "tracer_static tracer_static_jaeger tracer_static_lightstep tracer_static_datadog tracer_static_stackdriver" \ "continuous_profiler_stackdriver" \ ; do ( set -x; go build \ -tags "${build_tags}" \ ./... ) done labkit-v1.7.0/correlation/000077500000000000000000000000001410103036700154675ustar00rootroot00000000000000labkit-v1.7.0/correlation/base62.go000066400000000000000000000010701410103036700170760ustar00rootroot00000000000000package correlation import "bytes" const base62Chars string = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // encodeReverseBase62 encodes num into its Base62 reversed representation. // The most significant value is at the end of the string. // // Appending is faster than prepending and this is enough for the purpose of a random ID. func encodeReverseBase62(num int64) string { if num == 0 { return "0" } encoded := bytes.Buffer{} for q := num; q > 0; q /= 62 { encoded.Write([]byte{base62Chars[q%62]}) } return encoded.String() } labkit-v1.7.0/correlation/base62_test.go000066400000000000000000000010421410103036700201340ustar00rootroot00000000000000package correlation import ( "fmt" "testing" "github.com/stretchr/testify/require" ) func TestReverseBase62Conversion(t *testing.T) { tests := []struct { n int64 expected string }{ {n: 0, expected: "0"}, {n: 5, expected: "5"}, {n: 10, expected: "a"}, {n: 62, expected: "01"}, {n: 620, expected: "0a"}, {n: 6200, expected: "0C1"}, } for _, test := range tests { t.Run(fmt.Sprintf("%d_to_%s", test.n, test.expected), func(t *testing.T) { require.Equal(t, test.expected, encodeReverseBase62(test.n)) }) } } labkit-v1.7.0/correlation/context.go000066400000000000000000000032001410103036700174750ustar00rootroot00000000000000package correlation import ( "context" ) type ctxKey int const ( keyCorrelationID ctxKey = iota keyClientName ) func extractFromContextByKey(ctx context.Context, key ctxKey) string { value := ctx.Value(key) str, ok := value.(string) if !ok { return "" } return str } // ExtractFromContext extracts the CollectionID from the provided context. // Returns an empty string if it's unable to extract the CorrelationID for // any reason. func ExtractFromContext(ctx context.Context) string { return extractFromContextByKey(ctx, keyCorrelationID) } // ExtractFromContextOrGenerate extracts the CollectionID from the provided context or generates a random id if // context does not contain one. func ExtractFromContextOrGenerate(ctx context.Context) string { id := ExtractFromContext(ctx) if id == "" { id = SafeRandomID() } return id } // ContextWithCorrelation will create a new context containing the provided Correlation-ID value. // This can be extracted using ExtractFromContext. func ContextWithCorrelation(ctx context.Context, correlationID string) context.Context { return context.WithValue(ctx, keyCorrelationID, correlationID) } // ExtractClientNameFromContext extracts client name from incoming context. // It will return an empty string if client name does not exist in the context. func ExtractClientNameFromContext(ctx context.Context) string { return extractFromContextByKey(ctx, keyClientName) } // ContextWithClientName will create a new context containing client_name metadata. func ContextWithClientName(ctx context.Context, clientName string) context.Context { return context.WithValue(ctx, keyClientName, clientName) } labkit-v1.7.0/correlation/context_test.go000066400000000000000000000054641410103036700205520ustar00rootroot00000000000000package correlation import ( "context" "testing" "github.com/stretchr/testify/require" ) func TestExtractFromContext(t *testing.T) { require := require.New(t) tests := []struct { name string ctx context.Context want string }{ {"missing", context.Background(), ""}, {"set", context.WithValue(context.Background(), keyCorrelationID, "CORRELATION_ID"), "CORRELATION_ID"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(tt.want, ExtractFromContext(tt.ctx)) }) } } func TestExtractFromContextOrGenerate(t *testing.T) { require := require.New(t) tests := []struct { name string ctx context.Context want string }{ {"missing", context.Background(), ""}, {"set", context.WithValue(context.Background(), keyCorrelationID, "CORRELATION_ID"), "CORRELATION_ID"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.want == "" { // want a random one require.NotEmpty(ExtractFromContextOrGenerate(tt.ctx)) } else { require.Equal(tt.want, ExtractFromContextOrGenerate(tt.ctx)) } }) } } func TestContextWithCorrelation(t *testing.T) { require := require.New(t) tests := []struct { name string ctx context.Context correlationID string wantValue string }{ { name: "value", ctx: context.Background(), correlationID: "CORRELATION_ID", wantValue: "CORRELATION_ID", }, { name: "empty", ctx: context.Background(), correlationID: "", wantValue: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := ContextWithCorrelation(tt.ctx, tt.correlationID) gotValue := got.Value(keyCorrelationID) require.Equal(tt.wantValue, gotValue) }) } } func TestExtractClientNameFromContext(t *testing.T) { require := require.New(t) tests := []struct { name string ctx context.Context want string }{ {"missing", context.Background(), ""}, {"set", context.WithValue(context.Background(), keyClientName, "CLIENT_NAME"), "CLIENT_NAME"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { require.Equal(tt.want, ExtractClientNameFromContext(tt.ctx)) }) } } func TestContextWithClientName(t *testing.T) { require := require.New(t) tests := []struct { name string ctx context.Context clientName string wantValue string }{ { name: "value", ctx: context.Background(), clientName: "CLIENT_NAME", wantValue: "CLIENT_NAME", }, { name: "empty", ctx: context.Background(), clientName: "", wantValue: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := ContextWithClientName(tt.ctx, tt.clientName) gotValue := got.Value(keyClientName) require.Equal(tt.wantValue, gotValue) }) } } labkit-v1.7.0/correlation/doc.go000066400000000000000000000007401410103036700165640ustar00rootroot00000000000000/* Package correlation is the primary entrypoint into LabKit's correlation utilities. Provided Functionality Provides http middlewares for general purpose use cases: Generating a correlation-id for an incoming HTTP request using . Obtaining the correlation-id from the context. Injecting the correlation-id from the context into an outgoing request. Still to be implemented Extracting a correlation-id from an incoming HTTP request into the context. */ package correlation labkit-v1.7.0/correlation/examples_test.go000066400000000000000000000024541410103036700207000ustar00rootroot00000000000000package correlation_test import ( "context" "fmt" "log" "net/http" "gitlab.com/gitlab-org/labkit/correlation" ) func ExampleExtractFromContext_forLogging() { ctx := context.Background() correlationID := correlation.ExtractFromContext(ctx) log.Printf("event occurred. cid=%v", correlationID) } func ExampleInjectCorrelationID() { http.ListenAndServe(":8080", correlation.InjectCorrelationID( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { clientName := correlation.ExtractClientNameFromContext(r.Context()) fmt.Fprintf(w, clientName) }))) } func ExampleNewInstrumentedRoundTripper() { // correlation.NewInstrumentedRoundTripper will set the appropriate headers // on any outbound requests httpTransport := correlation.NewInstrumentedRoundTripper(http.DefaultTransport) httpClient := &http.Client{ Transport: httpTransport, } request, err := http.NewRequest("GET", "https://example.gitlab.com/api/v4/projects", nil) if err != nil { log.Fatalf("unable to send request: %v", err) } // Importantly, we need to set the context on the request ctx := correlation.ContextWithClientName( context.Background(), "clientname", ) request = request.WithContext(ctx) _, err = httpClient.Do(request) if err != nil { log.Fatalf("unable to read response: %v", err) } } labkit-v1.7.0/correlation/field.go000066400000000000000000000002151410103036700170770ustar00rootroot00000000000000package correlation // FieldName is the field used in structured logs that represents the correlationID. const FieldName = "correlation_id" labkit-v1.7.0/correlation/generator.go000066400000000000000000000036111410103036700200050ustar00rootroot00000000000000package correlation import ( "crypto/rand" "io" "sync" "time" "github.com/oklog/ulid/v2" ) // Replaceable for testing purposes. var ulidEntropySource io.Reader = &safeMonotonicReader{ delegate: ulid.Monotonic(rand.Reader, 0), } func generatePseudorandomCorrelationID() string { return "E:" + encodeReverseBase62(time.Now().UnixNano()) } // generateRandomCorrelationID will attempt to generate a correlationid randomly // if this fails, will log a message and fallback to a pseudorandom approach. func generateRandomCorrelationIDWithFallback() string { uid, err := ulid.New(ulid.Timestamp(time.Now()), ulidEntropySource) if err != nil { // Swallow the error and return a pseudorandom correlation_id. // Operators can determine that an error occurred by the shape of the // correlation_id, which will be prefixed with a `E:` return generatePseudorandomCorrelationID() } return uid.String() } // RandomID generates a random correlation ID. // Deprecated: use SafeRandomID instead. // Note, that this method will not return an error, it is here for compatibility reasons only. func RandomID() (string, error) { return generateRandomCorrelationIDWithFallback(), nil } // SafeRandomID generates a random correlation ID. func SafeRandomID() string { return generateRandomCorrelationIDWithFallback() } // safeMonotonicReader is a thread-safe wrapper around a ulid.Monotonic instance, which is not safe for concurrent use by itself. // See https://godoc.org/github.com/oklog/ulid#Monotonic. type safeMonotonicReader struct { mtx sync.Mutex delegate ulid.MonotonicReader } var _ ulid.MonotonicReader = &safeMonotonicReader{} func (r *safeMonotonicReader) MonotonicRead(ms uint64, p []byte) error { r.mtx.Lock() defer r.mtx.Unlock() return r.delegate.MonotonicRead(ms, p) } func (r *safeMonotonicReader) Read(p []byte) (int, error) { r.mtx.Lock() defer r.mtx.Unlock() return r.delegate.Read(p) } labkit-v1.7.0/correlation/generator_test.go000066400000000000000000000055701410103036700210520ustar00rootroot00000000000000package correlation import ( "bytes" "crypto/rand" "strings" "testing" "time" "github.com/oklog/ulid/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func emptyRandomSource() func() { oldEntropySource := ulidEntropySource // Use an empty random source, which will lead to ULID generation failure ulidEntropySource = &bytes.Buffer{} return func() { ulidEntropySource = oldEntropySource } } func requireValidRecentULID(require *require.Assertions, got string) { uid, err := ulid.Parse(got) require.NoError(err, "Expected correlationID to be a valid ULID, got %s", got) utime := ulid.Time(uid.Time()) diff := time.Since(utime) require.True(diff > 0, "Expected ULID to be generated in the past") require.True(diff < 1*time.Second, "Expected ULID to be generated with recent timestamp. Timestamp is %v", utime) } func TestRandom(t *testing.T) { require := require.New(t) got, err := RandomID() require.NoError(err, "Expected no error from RandomID") requireValidRecentULID(require, got) } func TestSafeRandom(t *testing.T) { t.Run("is valid", func(t *testing.T) { require := require.New(t) got := SafeRandomID() requireValidRecentULID(require, got) }) t.Run("is random", func(t *testing.T) { got1 := SafeRandomID() got2 := SafeRandomID() require.NotEqual(t, got1, got2) }) } func TestRandomEntropyFailure(t *testing.T) { restore := emptyRandomSource() defer restore() require := require.New(t) got, err := RandomID() require.NoError(err, "Expected no error from RandomID") require.NotEqual(got, "", "Expected a non-empty string response") require.True(strings.HasPrefix(got, "E:"), "Expecting fallback to pseudorandom correlationID") } func TestSafeRandomEntropyFailure(t *testing.T) { restore := emptyRandomSource() defer restore() require := require.New(t) got := SafeRandomID() require.NotEqual(got, "", "Expected a non-empty string response") require.True(strings.HasPrefix(got, "E:"), "Expecting fallback to pseudorandom correlationID") } // TestSafeMonotonicReader tests safeMonotonicReader for data races. It should be ran with -race. func TestSafeMonotonicReader(t *testing.T) { t.Run("MonotonicRead", func(t *testing.T) { r := safeMonotonicReader{ delegate: ulid.Monotonic(rand.Reader, 0), } go func() { d := make([]byte, 100) assert.NoError(t, r.MonotonicRead(100, d)) }() go func() { d := make([]byte, 100) assert.NoError(t, r.MonotonicRead(100, d)) }() }) t.Run("Read", func(t *testing.T) { r := safeMonotonicReader{ delegate: ulid.Monotonic(rand.Reader, 0), } go func() { d := make([]byte, 100) _, err := r.Read(d) assert.NoError(t, err) }() go func() { d := make([]byte, 100) _, err := r.Read(d) assert.NoError(t, err) }() }) } func BenchmarkSafeRandomID(b *testing.B) { // run the Fib function b.N times for n := 0; n < b.N; n++ { SafeRandomID() } } labkit-v1.7.0/correlation/grpc/000077500000000000000000000000001410103036700164225ustar00rootroot00000000000000labkit-v1.7.0/correlation/grpc/client_interceptors.go000066400000000000000000000036001410103036700230270ustar00rootroot00000000000000package grpccorrelation import ( "context" "gitlab.com/gitlab-org/labkit/correlation" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) func appendToOutgoingContext(ctx context.Context, clientName string) context.Context { correlationID := correlation.ExtractFromContext(ctx) if correlationID != "" { ctx = metadata.AppendToOutgoingContext(ctx, metadataCorrelatorKey, correlationID) } if clientName != "" { ctx = metadata.AppendToOutgoingContext(ctx, metadataClientNameKey, clientName) } return ctx } // UnaryClientCorrelationInterceptor propagates Correlation-IDs downstream. func UnaryClientCorrelationInterceptor(opts ...ClientCorrelationInterceptorOption) grpc.UnaryClientInterceptor { config := applyClientCorrelationInterceptorOptions(opts) return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { ctx = appendToOutgoingContext(ctx, config.clientName) return invoker(ctx, method, req, reply, cc, opts...) } } // StreamClientCorrelationInterceptor propagates Correlation-IDs downstream. func StreamClientCorrelationInterceptor(opts ...ClientCorrelationInterceptorOption) grpc.StreamClientInterceptor { config := applyClientCorrelationInterceptorOptions(opts) return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { ctx = appendToOutgoingContext(ctx, config.clientName) return streamer(ctx, desc, cc, method, opts...) } } // InjectToOutgoingContext will inject the correlation ID into the // outgoing context metadata. Repeat calls will overwrite any existing // correlation IDs. func InjectToOutgoingContext(ctx context.Context, correlationID string) context.Context { return metadata.AppendToOutgoingContext(ctx, metadataCorrelatorKey, correlationID) } labkit-v1.7.0/correlation/grpc/client_interceptors_options.go000066400000000000000000000013501410103036700246020ustar00rootroot00000000000000package grpccorrelation // The configuration for InjectCorrelationID. type clientInterceptConfig struct { clientName string } // ClientCorrelationInterceptorOption configures client correlation interceptors. type ClientCorrelationInterceptorOption func(*clientInterceptConfig) func applyClientCorrelationInterceptorOptions(opts []ClientCorrelationInterceptorOption) clientInterceptConfig { config := clientInterceptConfig{} for _, v := range opts { v(&config) } return config } // WithClientName will configure the client name metadata on the // GRPC client interceptors. func WithClientName(clientName string) ClientCorrelationInterceptorOption { return func(config *clientInterceptConfig) { config.clientName = clientName } } labkit-v1.7.0/correlation/grpc/client_interceptors_test.go000066400000000000000000000042521410103036700240720ustar00rootroot00000000000000package grpccorrelation import ( "context" "testing" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/labkit/correlation" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) const ( correlationID = "CORRELATION_ID" clientName = "CLIENT_NAME" methodName = "METHOD_NAME" ) func verifyContextMetadata(ctx context.Context, require *require.Assertions, expCorrelationID, expClientName string) { md, ok := metadata.FromOutgoingContext(ctx) require.True(ok) ids := md.Get(metadataCorrelatorKey) require.Less(0, len(ids)) require.Equal(expCorrelationID, ids[0]) clientNames := md.Get(metadataClientNameKey) require.Less(0, len(clientNames)) require.Equal(expClientName, clientNames[0]) } func getTestUnaryInvoker(require *require.Assertions, expCorrelationID, expClientName string) grpc.UnaryInvoker { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, opts ...grpc.CallOption) error { verifyContextMetadata(ctx, require, expCorrelationID, expClientName) return nil } } func getTestStreamer(require *require.Assertions, expCorrelationID, expClientName string) grpc.Streamer { return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { verifyContextMetadata(ctx, require, expCorrelationID, expClientName) return nil, nil } } func TestUnaryClientCorrelationInterceptor(t *testing.T) { require := require.New(t) clientInterceptor := UnaryClientCorrelationInterceptor(WithClientName(clientName)) ctx := correlation.ContextWithCorrelation(context.Background(), correlationID) err := clientInterceptor( ctx, methodName, nil, nil, nil, getTestUnaryInvoker(require, correlationID, clientName), ) require.NoError(err) } func TestStreamClientCorrelationInterceptor(t *testing.T) { require := require.New(t) clientInterceptor := StreamClientCorrelationInterceptor(WithClientName(clientName)) ctx := correlation.ContextWithCorrelation(context.Background(), correlationID) _, err := clientInterceptor( ctx, nil, nil, methodName, getTestStreamer(require, correlationID, clientName), ) require.NoError(err) } labkit-v1.7.0/correlation/grpc/examples_test.go000066400000000000000000000020351410103036700216260ustar00rootroot00000000000000package grpccorrelation_test import ( "log" "net" grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc" "google.golang.org/grpc" ) func Example_client() { // Add the interceptor to the grpc dialer dialer, err := grpc.Dial("https://gitaly-server.internal:9095", grpc.WithStreamInterceptor(grpccorrelation.StreamClientCorrelationInterceptor( grpccorrelation.WithClientName("my-client"), )), grpc.WithUnaryInterceptor(grpccorrelation.UnaryClientCorrelationInterceptor( grpccorrelation.WithClientName("my-client"), )), ) if err != nil { log.Fatalf("unable to dial: %v", err) } // Use the client connection with a protobuf service here... defer dialer.Close() } func Example_server() { server := grpc.NewServer( grpc.StreamInterceptor(grpccorrelation.StreamServerCorrelationInterceptor()), grpc.UnaryInterceptor(grpccorrelation.UnaryServerCorrelationInterceptor()), ) listener, err := net.Listen("unix", "/tmp/grpc") if err != nil { log.Fatalf("unable to listen: %v", err) } server.Serve(listener) } labkit-v1.7.0/correlation/grpc/key.go000066400000000000000000000002061410103036700175370ustar00rootroot00000000000000package grpccorrelation const ( metadataCorrelatorKey = "X-GitLab-Correlation-ID" metadataClientNameKey = "X-GitLab-Client-Name" ) labkit-v1.7.0/correlation/grpc/server_interceptors.go000066400000000000000000000052201410103036700230570ustar00rootroot00000000000000package grpccorrelation import ( "context" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "gitlab.com/gitlab-org/labkit/correlation" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) func extractFromContext(ctx context.Context, propagateIncomingCorrelationID bool) (context.Context, string) { var correlationID string md, ok := metadata.FromIncomingContext(ctx) if ok { if propagateIncomingCorrelationID { // Extract correlation_id correlationID = CorrelationIDFromMetadata(md) } // Extract client name clientNames := md.Get(metadataClientNameKey) if len(clientNames) > 0 { ctx = correlation.ContextWithClientName(ctx, clientNames[0]) } } if correlationID == "" { correlationID = correlation.SafeRandomID() } ctx = correlation.ContextWithCorrelation(ctx, correlationID) return ctx, correlationID } // CorrelationIDFromMetadata can be used to extract correlation ID from request/response metadata. // Returns an empty string if correlation ID is not found. func CorrelationIDFromMetadata(md metadata.MD) string { values := md.Get(metadataCorrelatorKey) if len(values) > 0 { return values[0] } return "" } // UnaryServerCorrelationInterceptor propagates Correlation-IDs from incoming upstream services. func UnaryServerCorrelationInterceptor(opts ...ServerCorrelationInterceptorOption) grpc.UnaryServerInterceptor { config := applyServerCorrelationInterceptorOptions(opts) return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { ctx, correlationID := extractFromContext(ctx, config.propagateIncomingCorrelationID) if config.reversePropagateCorrelationID { sts := grpc.ServerTransportStreamFromContext(ctx) err := sts.SetHeader(metadata.Pairs(metadataCorrelatorKey, correlationID)) if err != nil { return nil, err } } return handler(ctx, req) } } // StreamServerCorrelationInterceptor propagates Correlation-IDs from incoming upstream services. func StreamServerCorrelationInterceptor(opts ...ServerCorrelationInterceptorOption) grpc.StreamServerInterceptor { config := applyServerCorrelationInterceptorOptions(opts) return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { var correlationID string wrapped := grpc_middleware.WrapServerStream(ss) wrapped.WrappedContext, correlationID = extractFromContext(ss.Context(), config.propagateIncomingCorrelationID) if config.reversePropagateCorrelationID { err := wrapped.SetHeader(metadata.Pairs(metadataCorrelatorKey, correlationID)) if err != nil { return err } } return handler(srv, wrapped) } } labkit-v1.7.0/correlation/grpc/server_interceptors_options.go000066400000000000000000000030461410103036700246360ustar00rootroot00000000000000package grpccorrelation // The configuration for server correlation interceptors. type serverInterceptConfig struct { propagateIncomingCorrelationID bool reversePropagateCorrelationID bool } // ServerCorrelationInterceptorOption configures server correlation interceptor. type ServerCorrelationInterceptorOption func(*serverInterceptConfig) func applyServerCorrelationInterceptorOptions(opts []ServerCorrelationInterceptorOption) serverInterceptConfig { config := serverInterceptConfig{ propagateIncomingCorrelationID: true, // enabled by default } for _, v := range opts { v(&config) } return config } // WithoutPropagation disables correlation id propagation from incoming request metadata. // If the id is missing or the interceptor is configured to not propagate it, a new id is generated and // injected into the request context. func WithoutPropagation() ServerCorrelationInterceptorOption { return func(config *serverInterceptConfig) { config.propagateIncomingCorrelationID = false } } // WithReversePropagation enables server -> client correlation id propagation via response metadata. // Client can then use the returned correlation id e.g. for logging purposes. // It only makes sense to use this option together with WithoutPropagation i.e. in situations, when // client-supplied correlation id is not trusted so server generates its own one and hence clients doesn't have it. func WithReversePropagation() ServerCorrelationInterceptorOption { return func(config *serverInterceptConfig) { config.reversePropagateCorrelationID = true } } labkit-v1.7.0/correlation/grpc/server_interceptors_test.go000066400000000000000000000121541410103036700241220ustar00rootroot00000000000000package grpccorrelation import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/labkit/correlation" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) var ( _ grpc.ServerTransportStream = (*mockServerTransportStream)(nil) _ grpc.ServerStream = (*mockServerStream)(nil) ) type tcType struct { name string md metadata.MD withoutPropagation bool expectRandom bool expectedClientName string } func TestServerCorrelationInterceptors(t *testing.T) { tests := []tcType{ { name: "default", md: metadata.Pairs( metadataCorrelatorKey, correlationID, metadataClientNameKey, clientName, ), expectedClientName: clientName, }, { name: "id present but not trusted", md: metadata.Pairs( metadataCorrelatorKey, correlationID, ), withoutPropagation: true, expectRandom: true, }, { name: "id present, trusted but empty", md: metadata.Pairs( metadataCorrelatorKey, "", ), withoutPropagation: true, expectRandom: true, }, { name: "id absent and not trusted", md: metadata.Pairs(), withoutPropagation: true, expectRandom: true, }, { name: "id absent and trusted", md: metadata.Pairs(), expectRandom: true, }, { name: "no metadata", md: nil, expectRandom: true, }, } t.Run("unary", func(t *testing.T) { for _, tc := range tests { t.Run(tc.name, testUnaryServerCorrelationInterceptor(tc, false)) t.Run(tc.name+" (reverse)", testUnaryServerCorrelationInterceptor(tc, true)) } }) t.Run("streaming", func(t *testing.T) { for _, tc := range tests { t.Run(tc.name, testStreamingServerCorrelationInterceptor(tc, false)) t.Run(tc.name+" (reverse)", testStreamingServerCorrelationInterceptor(tc, true)) } }) } func testUnaryServerCorrelationInterceptor(tc tcType, reverseCorrelationID bool) func(*testing.T) { return func(t *testing.T) { t.Helper() sts := &mockServerTransportStream{} ctx := grpc.NewContextWithServerTransportStream(context.Background(), sts) if tc.md != nil { ctx = metadata.NewIncomingContext(ctx, tc.md) } interceptor := UnaryServerCorrelationInterceptor(constructServerOpts(tc, reverseCorrelationID)...) _, err := interceptor( ctx, nil, nil, func(ctx context.Context, req interface{}) (interface{}, error) { testServerCtx(ctx, t, tc, reverseCorrelationID, sts.header) return nil, nil }, ) require.NoError(t, err) } } func testStreamingServerCorrelationInterceptor(tc tcType, reverseCorrelationID bool) func(*testing.T) { return func(t *testing.T) { t.Helper() ctx := context.Background() if tc.md != nil { ctx = metadata.NewIncomingContext(ctx, tc.md) } ss := &mockServerStream{ ctx: ctx, } interceptor := StreamServerCorrelationInterceptor(constructServerOpts(tc, reverseCorrelationID)...) err := interceptor( nil, ss, nil, func(srv interface{}, stream grpc.ServerStream) error { testServerCtx(stream.Context(), t, tc, reverseCorrelationID, ss.header) return nil }, ) require.NoError(t, err) } } func constructServerOpts(tc tcType, reverseCorrelationID bool) []ServerCorrelationInterceptorOption { var opts []ServerCorrelationInterceptorOption if tc.withoutPropagation { opts = append(opts, WithoutPropagation()) } if reverseCorrelationID { opts = append(opts, WithReversePropagation()) } return opts } func testServerCtx(ctx context.Context, t *testing.T, tc tcType, reverseCorrelationID bool, header metadata.MD) { t.Helper() actualID := correlation.ExtractFromContext(ctx) if tc.expectRandom { assert.NotEqual(t, correlationID, actualID) assert.NotEmpty(t, actualID) } else { assert.Equal(t, correlationID, actualID) } vals := header.Get(metadataCorrelatorKey) if reverseCorrelationID { assert.Equal(t, []string{actualID}, vals) } else { assert.Empty(t, vals) } assert.Equal(t, tc.expectedClientName, correlation.ExtractClientNameFromContext(ctx)) } type mockServerTransportStream struct { header metadata.MD } func (s *mockServerTransportStream) Method() string { panic("implement me") } func (s *mockServerTransportStream) SetHeader(md metadata.MD) error { s.header = metadata.Join(s.header, md) return nil } func (s *mockServerTransportStream) SendHeader(md metadata.MD) error { panic("implement me") } func (s *mockServerTransportStream) SetTrailer(md metadata.MD) error { panic("implement me") } type mockServerStream struct { ctx context.Context header metadata.MD } func (s *mockServerStream) SetHeader(md metadata.MD) error { s.header = metadata.Join(s.header, md) return nil } func (s *mockServerStream) SendHeader(md metadata.MD) error { panic("implement me") } func (s *mockServerStream) SetTrailer(md metadata.MD) { panic("implement me") } func (s *mockServerStream) Context() context.Context { return s.ctx } func (s *mockServerStream) SendMsg(m interface{}) error { panic("implement me") } func (s *mockServerStream) RecvMsg(m interface{}) error { panic("implement me") } labkit-v1.7.0/correlation/inbound_http.go000066400000000000000000000035421410103036700205170ustar00rootroot00000000000000package correlation import ( "net" "net/http" "github.com/sebest/xff" ) // InjectCorrelationID is an HTTP middleware to generate an Correlation-ID for the incoming request, // or extract the existing Correlation-ID from the incoming request. By default, any upstream Correlation-ID, // passed in via the `X-Request-ID` header will be ignored. To enable this behaviour, the `WithPropagation` // option should be passed into the options. // Whether the Correlation-ID is generated or propagated, once inside this handler the request context // will have a Correlation-ID associated with it. func InjectCorrelationID(h http.Handler, opts ...InboundHandlerOption) http.Handler { config := applyInboundHandlerOptions(opts) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { parent := r.Context() correlationID := "" clientName := "" ipAddress := getRemoteIP(r, config) if config.shouldPropagate(ipAddress) { correlationID = r.Header.Get(propagationHeader) clientName = r.Header.Get(clientNameHeader) } if correlationID == "" { correlationID = SafeRandomID() } ctx := ContextWithCorrelation(parent, correlationID) if clientName != "" { ctx = ContextWithClientName(ctx, clientName) } if config.sendResponseHeader { // Set the response header. w.Header().Set(propagationHeader, correlationID) } h.ServeHTTP(w, r.WithContext(ctx)) }) } func getRemoteIP(r *http.Request, c inboundHandlerConfig) string { remoteAddr := r.RemoteAddr // Unix domain sockets have a remote addr of @. This will make the // xff package lookup the X-Forwarded-For address if available. if remoteAddr == "@" { r.RemoteAddr = "127.0.0.1:0" } if c.xffAllowed != nil { remoteAddr = xff.GetRemoteAddrIfAllowed(r, c.xffAllowed) } host, _, err := net.SplitHostPort(remoteAddr) if err != nil { return r.RemoteAddr } return host } labkit-v1.7.0/correlation/inbound_http_options.go000066400000000000000000000061641410103036700222750ustar00rootroot00000000000000package correlation import ( "net" "github.com/sirupsen/logrus" ) // XFFAllowedFunc decides whether X-Forwarded-For headers are to be trusted. type XFFAllowedFunc func(ip string) bool // The configuration for InjectCorrelationID. type inboundHandlerConfig struct { propagation bool sendResponseHeader bool invalidCIDRsForPropagation bool trustedCIDRsForPropagation []net.IPNet trustedCIDRsForXForwardedFor []net.IPNet xffAllowed XFFAllowedFunc } // InboundHandlerOption will configure a correlation handler // currently there are no options, but this gives us the option // to extend the interface in a backwards compatible way. type InboundHandlerOption func(*inboundHandlerConfig) func applyInboundHandlerOptions(opts []InboundHandlerOption) inboundHandlerConfig { config := inboundHandlerConfig{ propagation: false, } for _, v := range opts { v(&config) } return config } // WithPropagation will configure the handler to propagate existing correlation_ids // passed in from upstream services. // This is not the default behaviour. func WithPropagation() InboundHandlerOption { return func(config *inboundHandlerConfig) { config.propagation = true } } // WithSetResponseHeader will configure the handler to set the correlation_id // in the http response headers. func WithSetResponseHeader() InboundHandlerOption { return func(config *inboundHandlerConfig) { config.sendResponseHeader = true } } // WithCIDRsTrustedForPropagation will configure the handler to set a list of trusted // CIDR blocks to allow propagation of correlation_ids. func WithCIDRsTrustedForPropagation(trustedCIDRs []string) InboundHandlerOption { return func(config *inboundHandlerConfig) { for _, s := range trustedCIDRs { _, ipNet, err := net.ParseCIDR(s) if err != nil { logrus.Errorf("Bad trusted CIDR for propagation %s: %v, propagation disabled", s, err) config.invalidCIDRsForPropagation = true } else { config.trustedCIDRsForPropagation = append(config.trustedCIDRsForPropagation, *ipNet) } } } } // WithCIDRsTrustedForXForwardedFor will configure the handler to trust // X-Forwarded-For from trusted CIDR blocks. func WithCIDRsTrustedForXForwardedFor(trustedCIDRs []string) InboundHandlerOption { return func(config *inboundHandlerConfig) { for _, s := range trustedCIDRs { _, ipNet, err := net.ParseCIDR(s) if err != nil { logrus.Errorf("Bad trusted CIDR for XForwardedFor %s: %v", s, err) } else { config.trustedCIDRsForXForwardedFor = append(config.trustedCIDRsForXForwardedFor, *ipNet) } } config.xffAllowed = func(ip string) bool { return isTrustedIP(ip, config.trustedCIDRsForXForwardedFor) } } } func isTrustedIP(ipAddress string, trustedCIDRs []net.IPNet) bool { ip := net.ParseIP(ipAddress) for _, cidr := range trustedCIDRs { if cidr.Contains(ip) { return true } } return false } func (c *inboundHandlerConfig) shouldPropagate(ipAddress string) bool { if c.invalidCIDRsForPropagation { return false } return c.propagation && (len(c.trustedCIDRsForPropagation) == 0 || isTrustedIP(ipAddress, c.trustedCIDRsForPropagation)) } labkit-v1.7.0/correlation/inbound_http_test.go000066400000000000000000000202111410103036700215460ustar00rootroot00000000000000package correlation import ( "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/require" ) // nolint:gocognit,cyclop func TestInjectCorrelationID(t *testing.T) { tests := []struct { name string opts []InboundHandlerOption header http.Header shouldMatch string shouldNotMatch string shouldSetResponseHeader bool expectedResponseHeader string downstreamResponseHeaders http.Header remoteAddr string xffHeader string }{ { name: "without_propagation", }, { name: "without_propagation_ignore_incoming_header", header: map[string][]string{ propagationHeader: {"123"}, }, shouldNotMatch: "123", }, { name: "with_propagation_no_incoming_header", opts: []InboundHandlerOption{WithPropagation()}, }, { name: "with_propagation_incoming_header", opts: []InboundHandlerOption{WithPropagation()}, header: map[string][]string{ propagationHeader: {"123"}, }, shouldMatch: "123", }, { name: "with_propagation_double_incoming_header", opts: []InboundHandlerOption{WithPropagation()}, header: map[string][]string{ propagationHeader: {"123", "456"}, }, shouldMatch: "123", }, { name: "with_set_response_header", opts: []InboundHandlerOption{WithSetResponseHeader()}, shouldSetResponseHeader: true, }, { name: "with_set_response_header_and_with_propagation_incoming_header", opts: []InboundHandlerOption{WithSetResponseHeader(), WithPropagation()}, shouldSetResponseHeader: true, }, { name: "with_set_response_header_and_with_propagation_incoming_header_set", opts: []InboundHandlerOption{WithSetResponseHeader(), WithPropagation()}, shouldSetResponseHeader: true, header: map[string][]string{ propagationHeader: {"123"}, }, expectedResponseHeader: "123", }, { name: "mismatching_correlation_ids_without_propagation", opts: []InboundHandlerOption{}, downstreamResponseHeaders: map[string][]string{ propagationHeader: {"CLIENT_CORRELATION_ID"}, }, shouldSetResponseHeader: true, expectedResponseHeader: "CLIENT_CORRELATION_ID", }, { name: "mismatching_correlation_ids_with_propagation", opts: []InboundHandlerOption{WithSetResponseHeader()}, downstreamResponseHeaders: map[string][]string{ propagationHeader: {"CLIENT_CORRELATION_ID"}, }, shouldSetResponseHeader: true, expectedResponseHeader: "CLIENT_CORRELATION_ID", }, { name: "with_set_response_header_and_with_propagation_incoming_header_set", opts: []InboundHandlerOption{WithSetResponseHeader(), WithPropagation()}, shouldSetResponseHeader: true, header: map[string][]string{ propagationHeader: {"123"}, }, downstreamResponseHeaders: map[string][]string{ propagationHeader: {"CLIENT_CORRELATION_ID"}, }, expectedResponseHeader: "CLIENT_CORRELATION_ID", }, { name: "trusted_cidrs_and_propagation", opts: []InboundHandlerOption{ WithCIDRsTrustedForXForwardedFor([]string{"192.168.0.1/32"}), WithCIDRsTrustedForPropagation([]string{"1.2.3.4/32"}), WithPropagation(), }, xffHeader: "1.2.3.4, 127.0.0.1", remoteAddr: "192.168.0.1:1024", header: map[string][]string{ propagationHeader: {"123"}, }, shouldMatch: "123", }, { name: "untrusted_xff_cidr_propagation", opts: []InboundHandlerOption{ WithCIDRsTrustedForXForwardedFor([]string{"1.2.3.4/32"}), WithCIDRsTrustedForPropagation([]string{"1.2.3.5/32"}), WithPropagation(), }, xffHeader: "1.2.3.5, 127.0.0.1", remoteAddr: "192.168.0.1:1024", header: map[string][]string{ propagationHeader: {"123"}, }, shouldNotMatch: "123", }, { name: "trusted_xff_cidr_untrusted_propagation_cidr", opts: []InboundHandlerOption{ WithCIDRsTrustedForXForwardedFor([]string{"192.168.0.1/32"}), WithCIDRsTrustedForPropagation([]string{"127.0.0.1/32"}), WithPropagation(), }, xffHeader: "1.2.3.5, 127.0.0.1", remoteAddr: "192.168.0.1:1024", header: map[string][]string{ propagationHeader: {"123"}, }, shouldNotMatch: "123", }, { name: "trusted_cidrs_no_propagation", opts: []InboundHandlerOption{ WithCIDRsTrustedForXForwardedFor([]string{"192.168.0.1/32"}), WithCIDRsTrustedForPropagation([]string{"1.2.3.4/32"}), }, xffHeader: "1.2.3.4, 127.0.0.1", remoteAddr: "192.168.0.1:1024", header: map[string][]string{ propagationHeader: {"123"}, }, shouldNotMatch: "123", }, { name: "trusted_propagation_cidr_remote_addr_propagation", opts: []InboundHandlerOption{ WithCIDRsTrustedForPropagation([]string{"1.2.3.4/32"}), WithPropagation(), }, remoteAddr: "1.2.3.4:1024", header: map[string][]string{ propagationHeader: {"123"}, }, shouldMatch: "123", }, { name: "no_xff_trusted_cidrs", opts: []InboundHandlerOption{ WithCIDRsTrustedForPropagation([]string{"192.168.0.1/8"}), WithPropagation(), }, xffHeader: "1.2.3.4, 127.0.0.1", remoteAddr: "192.168.0.1:1024", header: map[string][]string{ propagationHeader: {"123"}, }, shouldMatch: "123", }, { name: "xff_trusted_cidrs_with_propagation_unix_socket", opts: []InboundHandlerOption{ WithCIDRsTrustedForXForwardedFor([]string{"192.168.0.1/8", "127.0.0.1/32"}), WithCIDRsTrustedForPropagation([]string{"1.2.3.4/32"}), WithPropagation(), }, xffHeader: "1.2.3.4, 127.0.0.1", remoteAddr: "@", header: map[string][]string{ propagationHeader: {"123"}, }, shouldMatch: "123", }, { name: "no_xff_with_propagation_unix_socket", opts: []InboundHandlerOption{ WithCIDRsTrustedForPropagation([]string{"1.2.3.4/8"}), WithPropagation(), }, xffHeader: "1.2.3.4, 127.0.0.1", remoteAddr: "@", header: map[string][]string{ propagationHeader: {"123"}, }, shouldNotMatch: "123", }, { name: "bad_cidrs_ignore_propagation", opts: []InboundHandlerOption{ WithCIDRsTrustedForXForwardedFor([]string{"a.b.c"}), WithCIDRsTrustedForPropagation([]string{"x.y.z"}), WithPropagation(), }, xffHeader: "1.2.3.4, 127.0.0.1", remoteAddr: "192.168.1.2:9876", header: map[string][]string{ propagationHeader: {"123"}, }, shouldNotMatch: "123", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { invoked := false h := InjectCorrelationID(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { invoked = true ctx := r.Context() correlationID := ExtractFromContext(ctx) require.NotNil(t, correlationID, "CorrelationID is missing") require.NotEmpty(t, correlationID, "CorrelationID is missing") if test.shouldMatch != "" { require.Equal(t, test.shouldMatch, correlationID, "CorrelationID should match") } if test.shouldNotMatch != "" { require.NotEqual(t, test.shouldNotMatch, correlationID, "CorrelationID should not match") } for k, vs := range test.downstreamResponseHeaders { for _, v := range vs { w.Header().Set(k, v) } } }), test.opts...) r := httptest.NewRequest("GET", "http://example.com", nil) for k, v := range test.header { r.Header[http.CanonicalHeaderKey(k)] = v } if test.remoteAddr != "" { r.RemoteAddr = test.remoteAddr } if test.xffHeader != "" { r.Header.Add("X-Forwarded-For", test.xffHeader) } w := httptest.NewRecorder() h.ServeHTTP(w, r) require.True(t, invoked, "handler not executed") v, ok := w.Result().Header[http.CanonicalHeaderKey(propagationHeader)] require.Equal(t, test.shouldSetResponseHeader, ok, "response header existence mismatch") if test.shouldSetResponseHeader { require.Len(t, v, 1, "expected exactly one correlation header") require.NotEmpty(t, v[0], "expected non-empty correlation header") } if test.expectedResponseHeader != "" { responseHeader := w.Header().Get(propagationHeader) require.Equal(t, test.expectedResponseHeader, responseHeader, "response header should match") } }) } } labkit-v1.7.0/correlation/outbound_http.go000066400000000000000000000025531410103036700207210ustar00rootroot00000000000000package correlation import ( "net/http" ) const ( propagationHeader = "X-Request-ID" clientNameHeader = "X-GitLab-Client-Name" ) type instrumentedRoundTripper struct { delegate http.RoundTripper config instrumentedRoundTripperConfig } // injectRequest will pass the CorrelationId through to a downstream http request // for propagation. func (c instrumentedRoundTripper) injectRequest(req *http.Request) { correlationID := ExtractFromContext(req.Context()) if correlationID != "" { req.Header.Set(propagationHeader, correlationID) } if c.config.clientName != "" { req.Header.Set(clientNameHeader, c.config.clientName) } } func (c instrumentedRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) { c.injectRequest(req) return c.delegate.RoundTrip(req) } // NewInstrumentedRoundTripper acts as a "client-middleware" for outbound http requests // adding instrumentation to the outbound request and then delegating to the underlying // transport. // // If will extract the current Correlation-ID from the request context and pass this via // the X-Request-ID request header to downstream services. func NewInstrumentedRoundTripper(delegate http.RoundTripper, opts ...InstrumentedRoundTripperOption) http.RoundTripper { config := applyInstrumentedRoundTripperOptions(opts) return &instrumentedRoundTripper{delegate: delegate, config: config} } labkit-v1.7.0/correlation/outbound_http_options.go000066400000000000000000000015521410103036700224720ustar00rootroot00000000000000package correlation // The configuration for InjectCorrelationID. type instrumentedRoundTripperConfig struct { clientName string } // InstrumentedRoundTripperOption will configure a correlation handler // currently there are no options, but this gives us the option // to extend the interface in a backwards compatible way. type InstrumentedRoundTripperOption func(*instrumentedRoundTripperConfig) func applyInstrumentedRoundTripperOptions(opts []InstrumentedRoundTripperOption) instrumentedRoundTripperConfig { config := instrumentedRoundTripperConfig{} for _, v := range opts { v(&config) } return config } // WithClientName will configure the X-GitLab-Client-Name header on the // http client. func WithClientName(clientName string) InstrumentedRoundTripperOption { return func(config *instrumentedRoundTripperConfig) { config.clientName = clientName } } labkit-v1.7.0/correlation/outbound_http_test.go000066400000000000000000000107221410103036700217550ustar00rootroot00000000000000package correlation import ( "context" "errors" "net/http" "testing" "github.com/stretchr/testify/require" ) var httpCorrelationTests = []struct { name string ctx context.Context correlationID string clientName string hasHeader bool }{ { name: "context with value", ctx: context.Background(), correlationID: "CORRELATION_ID", clientName: "test_client", hasHeader: true, }, { name: "context without value", ctx: context.Background(), correlationID: "", clientName: "", hasHeader: false, }, } func Test_injectRequest(t *testing.T) { for _, tt := range httpCorrelationTests { t.Run(tt.name, func(t *testing.T) { require := require.New(t) ctx := context.WithValue(tt.ctx, keyCorrelationID, tt.correlationID) req, err := http.NewRequest("GET", "http://example.com", nil) require.NoError(err) req = req.WithContext(ctx) mockTransport := roundTripperFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{}, nil }) roundTripper := NewInstrumentedRoundTripper(mockTransport, WithClientName(tt.clientName)) roundTripper.(*instrumentedRoundTripper).injectRequest(req) value := req.Header.Get(propagationHeader) clientName := req.Header.Get(clientNameHeader) require.True(tt.hasHeader == (value != ""), "Expected header existence %v. Instead got header %v", tt.hasHeader, value) require.Equal(tt.clientName, clientName, "Expected client name value %v, got %v", tt.clientName, clientName) if tt.hasHeader { require.Equal(tt.correlationID, value, "Expected header value %v, got %v", tt.correlationID, value) } }) } } type delegatedRoundTripper struct { delegate func(req *http.Request) (*http.Response, error) } func (c delegatedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { return c.delegate(req) } func roundTripperFunc(delegate func(req *http.Request) (*http.Response, error)) http.RoundTripper { return &delegatedRoundTripper{delegate} } func TestInstrumentedRoundTripper(t *testing.T) { for _, tt := range httpCorrelationTests { t.Run(tt.name, func(t *testing.T) { require := require.New(t) response := &http.Response{} mockTransport := roundTripperFunc(func(req *http.Request) (*http.Response, error) { value := req.Header.Get(propagationHeader) require.True(tt.hasHeader == (value != ""), "Expected header existence %v. Instead got header %v", tt.hasHeader, value) if tt.hasHeader { require.Equal(tt.correlationID, value, "Expected header value %v, got %v", tt.correlationID, value) } return response, nil }) client := &http.Client{ Transport: NewInstrumentedRoundTripper(mockTransport), } ctx := context.WithValue(tt.ctx, keyCorrelationID, tt.correlationID) req, err := http.NewRequest("GET", "http://example.com", nil) require.NoError(err) req = req.WithContext(ctx) res, err := client.Do(req) require.NoError(err) require.Equal(response, res) }) } } func TestInstrumentedRoundTripperFailures(t *testing.T) { for _, tt := range httpCorrelationTests { t.Run(tt.name+" - with errors", func(t *testing.T) { require := require.New(t) testErr := errors.New("test") mockTransport := roundTripperFunc(func(req *http.Request) (*http.Response, error) { value := req.Header.Get(propagationHeader) require.True(tt.hasHeader == (value != ""), "Expected header existence %v. Instead got header %v", tt.hasHeader, value) if tt.hasHeader { require.Equal(tt.correlationID, value, "Expected header value %v, got %v", tt.correlationID, value) } return nil, testErr }) client := &http.Client{ Transport: NewInstrumentedRoundTripper(mockTransport), } ctx := context.WithValue(tt.ctx, keyCorrelationID, tt.correlationID) req, err := http.NewRequest("GET", "http://example.com", nil) require.NoError(err) req = req.WithContext(ctx) res, err := client.Do(req) require.Error(err) require.Nil(res) }) } } func TestInstrumentedRoundTripperWithoutContext(t *testing.T) { require := require.New(t) response := &http.Response{} mockTransport := roundTripperFunc(func(req *http.Request) (*http.Response, error) { return response, nil }) client := &http.Client{ Transport: NewInstrumentedRoundTripper(mockTransport), } req, err := http.NewRequest("GET", "http://example.com", nil) require.NoError(err) res, err := client.Do(req) require.NoError(err) require.Equal(response, res) } labkit-v1.7.0/correlation/raven/000077500000000000000000000000001410103036700166025ustar00rootroot00000000000000labkit-v1.7.0/correlation/raven/doc.go000066400000000000000000000002521410103036700176750ustar00rootroot00000000000000// Package raven is allows correlation information to be added to raven requests. // // Deprecated: Use gitlab.com/gitlab-org/labkit/errortracking instead. package raven labkit-v1.7.0/correlation/raven/examples_test.go000066400000000000000000000007271410103036700220140ustar00rootroot00000000000000package raven_test import ( "context" "fmt" raven "github.com/getsentry/raven-go" correlation "gitlab.com/gitlab-org/labkit/correlation/raven" ) func Example() { // In reality, this would be passed into the function ctx := context.Background() err := fmt.Errorf("An error occurred") client := raven.DefaultClient extra := correlation.SetExtra(ctx, nil) packet := raven.NewPacketWithExtra(fmt.Sprintf("error: %v", err), extra) client.Capture(packet, nil) } labkit-v1.7.0/correlation/raven/extras.go000066400000000000000000000012241410103036700204360ustar00rootroot00000000000000package raven import ( "context" raven "github.com/getsentry/raven-go" "gitlab.com/gitlab-org/labkit/correlation" ) const ravenSentryExtraKey = "gitlab.CorrelationID" // SetExtra will augment a raven message with the CorrelationID. // An existing `extra` can be passed in, but if it's nil // a new one will be created. // // Deprecated: Use gitlab.com/gitlab-org/labkit/errortracking instead. func SetExtra(ctx context.Context, extra raven.Extra) raven.Extra { if extra == nil { extra = raven.Extra{} } correlationID := correlation.ExtractFromContext(ctx) if correlationID != "" { extra[ravenSentryExtraKey] = correlationID } return extra } labkit-v1.7.0/correlation/raven/extras_test.go000066400000000000000000000017131410103036700215000ustar00rootroot00000000000000package raven import ( "context" "reflect" "testing" raven "github.com/getsentry/raven-go" "gitlab.com/gitlab-org/labkit/correlation" ) func TestSetExtra(t *testing.T) { tests := []struct { name string ctx context.Context extra raven.Extra want raven.Extra }{ { name: "context", ctx: correlation.ContextWithCorrelation(context.Background(), "C001"), extra: map[string]interface{}{ "key": "value", }, want: map[string]interface{}{ "key": "value", ravenSentryExtraKey: "C001", }, }, { name: "no_injected_extras", ctx: correlation.ContextWithCorrelation(context.Background(), "C001"), extra: nil, want: map[string]interface{}{ ravenSentryExtraKey: "C001", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := SetExtra(tt.ctx, tt.extra); !reflect.DeepEqual(got, tt.want) { t.Errorf("SetExtra() = %v, want %v", got, tt.want) } }) } } labkit-v1.7.0/doc.go000066400000000000000000000001601410103036700142370ustar00rootroot00000000000000/* Package labkit is a minimalist library to provide functionality for Go services at GitLab. */ package labkit labkit-v1.7.0/downstream-vendor.sh000077500000000000000000000101501410103036700171600ustar00rootroot00000000000000#!/usr/bin/env bash ################################################### # Downstream Vendor Utility ################################################### # This script will attempt to vendor the latest # version of labkit into downstream projects # # This script should only be run on master (for now) # It assumed that the person executing the script # has Git+SSH push access to GitLab repositories on # gitlab.com # # A new merge request will be created for each # downstream project ################################################### set -euo pipefail IFS=$'\n\t' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" DOWNSTREAM_PROJECTS=( "gitlab-org/gitlab-workhorse" "gitlab-org/gitaly" "gitlab-org/gitlab-pages" "gitlab-org/gitlab-shell" "gitlab-org/container-registry" "gitlab-org/cluster-integration/gitlab-agent" ) MERGE_REQUESTS=() LABKIT_VERSION_TAG=$(git describe --tags --abbrev=0) WORKING_DIR=$(mktemp -d) trap '{ rm -rf "${WORKING_DIR}"; }' EXIT pushd "${WORKING_DIR}" # Pushes to a remote and returns the GitLab Merge Request IID function git_push_and_return_merge_request_iid() { # This could be done more elegantly with gnu-grep, but we can't assume that # everyone user (macos users mainly) will have it git push "$@" 2>&1 >/dev/null | grep /merge_requests/ | sed 's#.*/merge_requests/##;s# ##g' } # Fetch the description from the Releases API function get_labkit_release_summary() { curl --silent --fail \ "https://gitlab.com/api/v4/projects/gitlab-org%2flabkit/releases/${LABKIT_VERSION_TAG}" | jq -r '.description' | grep -v '^#' | grep -v '^$' } function vendor_downstream_project() { local project=$1 local path=${project##*/} local branch_name="vendor-$LABKIT_VERSION_TAG" local vendor_branch_exists local merge_request_iid local requires_amend=no local summary echo "# cloning ${project}" git clone "git@gitlab.com:${project}.git" "${path}" pushd "${path}" if git ls-remote --exit-code --heads origin "${branch_name}"; then vendor_branch_exists=yes git checkout -b "${branch_name}" "origin/${branch_name}" else vendor_branch_exists=no git checkout -b "${branch_name}" fi # Update to the appropriate commit SHA go get -u=patch "gitlab.com/gitlab-org/labkit@$LABKIT_VERSION_TAG" go mod tidy git add go.mod go.sum if [[ "$vendor_branch_exists" == "yes" ]]; then git commit --amend --no-edit merge_request_iid=$(git_push_and_return_merge_request_iid --force-with-lease) echo "# updated merge request https://gitlab.com/${project}/-/merge_requests/${merge_request_iid}" else summary=$(get_labkit_release_summary) git commit -m "Update LabKit library to $LABKIT_VERSION_TAG ${summary} See https://gitlab.com/gitlab-org/labkit/-/releases/$LABKIT_VERSION_TAG" merge_request_iid=$( git_push_and_return_merge_request_iid \ -o merge_request.create \ -o merge_request.target=master \ -o merge_request.remove_source_branch \ --set-upstream origin "$(git rev-parse --abbrev-ref HEAD)" ) echo "# created merge request https://gitlab.com/${project}/-/merge_requests/${merge_request_iid}" fi MERGE_REQUESTS+=("https://gitlab.com/${project}/-/merge_requests/${merge_request_iid}") # Add a changelog entry? if [[ -x _support/changelog ]]; then if _support/changelog \ --merge-request "${merge_request_iid}" \ --type other \ "Update LabKit to $LABKIT_VERSION_TAG"; then requires_amend=yes git add "changelogs/unreleased/*" fi fi ## Temporary hack until we come up with a better solution if [[ "$project" == "gitlab-org/cluster-integration/gitlab-agent" ]]; then if make update-repos; then requires_amend=yes git status git add "build/*" fi fi if [[ "$requires_amend" == "yes" ]]; then git commit --amend --no-edit git push --force-with-lease fi popd } for project in "${DOWNSTREAM_PROJECTS[@]}"; do vendor_downstream_project "${project}" done echo "####################################" echo "# completed successfully" for mr_url in "${MERGE_REQUESTS[@]}"; do echo "# ${mr_url}" done labkit-v1.7.0/errortracking/000077500000000000000000000000001410103036700160225ustar00rootroot00000000000000labkit-v1.7.0/errortracking/capture.go000066400000000000000000000002561410103036700200170ustar00rootroot00000000000000package errortracking // Capture reports an error to the error reporting service. func Capture(err error, opts ...CaptureOption) { DefaultTracker().Capture(err, opts...) } labkit-v1.7.0/errortracking/capture_context.go000066400000000000000000000007641410103036700215670ustar00rootroot00000000000000package errortracking import ( "context" "github.com/getsentry/sentry-go" "gitlab.com/gitlab-org/labkit/correlation" ) const sentryExtraKey = "gitlab.CorrelationID" // WithContext will extract information from the context to add to the error. func WithContext(ctx context.Context) CaptureOption { return func(config *captureConfig, event *sentry.Event) { correlationID := correlation.ExtractFromContext(ctx) if correlationID != "" { event.Tags[sentryExtraKey] = correlationID } } } labkit-v1.7.0/errortracking/capture_field.go000066400000000000000000000004201410103036700211530ustar00rootroot00000000000000package errortracking import ( "github.com/getsentry/sentry-go" ) // WithField allows to add a custom field to the error. func WithField(key string, value string) CaptureOption { return func(config *captureConfig, event *sentry.Event) { event.Tags[key] = value } } labkit-v1.7.0/errortracking/capture_field_test.go000066400000000000000000000005241410103036700222170ustar00rootroot00000000000000package errortracking import ( "testing" "github.com/getsentry/sentry-go" "github.com/stretchr/testify/require" ) func TestWithField(t *testing.T) { event := sentry.NewEvent() domain := "http://example.com" config := &captureConfig{} WithField("domain", domain)(config, event) require.True(t, event.Tags["domain"] == domain) } labkit-v1.7.0/errortracking/capture_options.go000066400000000000000000000007101410103036700215650ustar00rootroot00000000000000package errortracking import ( "github.com/getsentry/sentry-go" ) // CaptureOption will configure how an error is captured. type CaptureOption func(*captureConfig, *sentry.Event) type captureConfig struct{} func applyCaptureOptions(opts []CaptureOption) (captureConfig, *sentry.Event) { event := sentry.NewEvent() event.Level = sentry.LevelError config := captureConfig{} for _, v := range opts { v(&config, event) } return config, event } labkit-v1.7.0/errortracking/capture_options_test.go000066400000000000000000000015621410103036700226320ustar00rootroot00000000000000package errortracking import ( "testing" "github.com/getsentry/sentry-go" "github.com/stretchr/testify/require" ) func Test_applyCaptureOptions(t *testing.T) { tests := []struct { name string opts []CaptureOption wantConfig captureConfig wantEventLevel sentry.Level }{ { name: "default", opts: []CaptureOption{}, wantConfig: captureConfig{}, wantEventLevel: sentry.LevelError, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotConfig, gotEvent := applyCaptureOptions(tt.opts) gotEventLevel := gotEvent.Level require.Equalf(t, gotConfig, tt.wantConfig, "applyCaptureOptions() = %v, want %v", gotConfig, tt.wantConfig) require.Equalf(t, gotEventLevel, tt.wantEventLevel, "applyCaptureOptions() Event.Level = %v, want %v", gotEventLevel, tt.wantEventLevel) }) } } labkit-v1.7.0/errortracking/capture_request.go000066400000000000000000000021411410103036700215620ustar00rootroot00000000000000package errortracking import ( "net/http" "github.com/getsentry/sentry-go" "gitlab.com/gitlab-org/labkit/mask" ) // WithRequest will capture details of the request along with the error. func WithRequest(r *http.Request) CaptureOption { return func(config *captureConfig, event *sentry.Event) { event.Request = redactRequestInfo(r) event.Request.URL = r.URL.String() event.Request.Headers["host"] = r.URL.Hostname() event.Request.Method = r.Method } } // redactRequestInfo strips out information that shouldn't be send by the client. func redactRequestInfo(r *http.Request) *sentry.Request { if r == nil { return &sentry.Request{} } req := &sentry.Request{ Headers: make(map[string]string), } for header, v := range r.Header { req.Headers[header] = v[0] if mask.IsSensitiveHeader(header) { req.Headers[header] = mask.RedactionString } } params := r.URL.Query() for paramName := range params { if mask.IsSensitiveParam(paramName) { for i := range params[paramName] { params[paramName][i] = mask.RedactionString } } } req.QueryString = params.Encode() return req } labkit-v1.7.0/errortracking/capture_request_test.go000066400000000000000000000077421410103036700226350ustar00rootroot00000000000000package errortracking import ( "fmt" "net/http/httptest" "net/url" "reflect" "testing" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/labkit/mask" ) func TestCleanHeaders(t *testing.T) { tests := []struct { name string url string headers map[string]string wantHeaders map[string]string wantQuery string }{ { name: "trivial", url: "/api/projects", headers: map[string]string{}, wantHeaders: map[string]string{}, wantQuery: "", }, { name: "no_secrets", url: "/api/projects?app=gitlab", headers: map[string]string{ "User-Agent": "golang", }, wantHeaders: map[string]string{ "User-Agent": "golang", }, wantQuery: "app=gitlab", }, { name: "authorization_secret", url: "/api/projects?authorization=secret1", headers: map[string]string{ "User-Agent": "golang", "Authorization": "secret2", }, wantHeaders: map[string]string{ "User-Agent": "golang", "Authorization": mask.RedactionString, }, wantQuery: fmt.Sprintf("authorization=%s", url.QueryEscape(mask.RedactionString)), }, { name: "private_token_secret", url: "/api/projects?private-token=secret1", headers: map[string]string{ "User-Agent": "golang", "Private-Token": "secret2", }, wantHeaders: map[string]string{ "User-Agent": "golang", "Private-Token": mask.RedactionString, }, wantQuery: fmt.Sprintf("private-token=%s", url.QueryEscape(mask.RedactionString)), }, { name: "ending_with_token_secret", url: "/api/projects?Sometoken=abc", headers: map[string]string{ "User-Agent": "golang", "Sometoken": "token", }, wantHeaders: map[string]string{ "User-Agent": "golang", "Sometoken": mask.RedactionString, }, wantQuery: fmt.Sprintf("Sometoken=%s", url.QueryEscape(mask.RedactionString)), }, { name: "containing_password_secret", url: "/api/projects?my_password_secret=pa55word", headers: map[string]string{ "User-Agent": "golang", "X-Personal-Password-Secret": "password", }, wantHeaders: map[string]string{ "User-Agent": "golang", "X-Personal-Password-Secret": mask.RedactionString, }, wantQuery: fmt.Sprintf("my_password_secret=%s", url.QueryEscape(mask.RedactionString)), }, { name: "ending_key_secret", url: "/api/projects?my_key=1212", headers: map[string]string{ "User-Agent": "golang", "Personalvaultkey": "Key", }, wantHeaders: map[string]string{ "User-Agent": "golang", "Personalvaultkey": mask.RedactionString, }, wantQuery: fmt.Sprintf("my_key=%s", url.QueryEscape(mask.RedactionString)), }, { name: "containing_key", url: "/api/projects", headers: map[string]string{ "User-Agent": "golang", "Keypassuser": "Not-Secret-String", }, wantHeaders: map[string]string{ "User-Agent": "golang", "Keypassuser": "Not-Secret-String", }, }, { name: "multiple_secrets", url: "/api/projects?token=123&token=456", headers: map[string]string{ "User-Agent": "golang", "Authorization": "secret1", "Private-Token": "secret2", }, wantHeaders: map[string]string{ "User-Agent": "golang", "Authorization": mask.RedactionString, "Private-Token": mask.RedactionString, }, wantQuery: fmt.Sprintf("token=%s&token=%s", url.QueryEscape(mask.RedactionString), url.QueryEscape(mask.RedactionString)), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest("GET", tt.url, nil) for k, v := range tt.headers { req.Header.Set(k, v) } result := redactRequestInfo(req) processedRequestHeaders := result.Headers require.True(t, reflect.DeepEqual(tt.wantHeaders, processedRequestHeaders), "Expected %+v, got %+v", tt.wantHeaders, processedRequestHeaders) require.Equal(t, tt.wantQuery, result.QueryString, "Expected query %+v, got %+v", tt.wantQuery, result.QueryString) }) } } labkit-v1.7.0/errortracking/doc.go000066400000000000000000000002041410103036700171120ustar00rootroot00000000000000/* Package errortracking abstracts all error reporting features away from services like workhorse or pages */ package errortracking labkit-v1.7.0/errortracking/examples_test.go000066400000000000000000000014531410103036700212310ustar00rootroot00000000000000package errortracking_test import ( "net/http" "gitlab.com/gitlab-org/labkit/errortracking" ) func ExampleCapture() { req, err := http.NewRequest("GET", "http://example.com", nil) ctx := req.Context() if err != nil { // Send the error to the error tracking system errortracking.Capture(err, errortracking.WithContext(ctx), // Extract details such as correlation-id from context errortracking.WithRequest(req), // Extract additional details from request errortracking.WithField("domain", "http://example.com")) // Add additional custom details } } func ExampleNewHandler() { handler := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { panic("oh dear") }) http.ListenAndServe(":1234", errortracking.NewHandler(handler)) } labkit-v1.7.0/errortracking/http_handler.go000066400000000000000000000006301410103036700210240ustar00rootroot00000000000000package errortracking import ( "net/http" sentryhttp "github.com/getsentry/sentry-go/http" ) // NewHandler will recover from panics inside handlers and reports the stacktrace to the errorreporting provider. func NewHandler(next http.Handler) http.Handler { sentryHandler := sentryhttp.New(sentryhttp.Options{ Repanic: false, WaitForDelivery: true, }) return sentryHandler.Handle(next) } labkit-v1.7.0/errortracking/initialization.go000066400000000000000000000006501410103036700214010ustar00rootroot00000000000000package errortracking import ( "github.com/getsentry/sentry-go" ) type InitializationOption = TrackerOption // Initialize initializes global error tracking. // Call this once on the program start. func Initialize(opts ...InitializationOption) error { err := sentry.Init(trackerOptionsToSentryClientOptions(opts...)) if err != nil { return err } defaultTracker = newSentryTracker(sentry.CurrentHub()) return nil } labkit-v1.7.0/errortracking/sentry_tracker.go000066400000000000000000000010261410103036700214070ustar00rootroot00000000000000package errortracking import ( "reflect" "github.com/getsentry/sentry-go" ) type sentryTracker struct { hub *sentry.Hub } func newSentryTracker(hub *sentry.Hub) *sentryTracker { return &sentryTracker{ hub: hub, } } func (s *sentryTracker) Capture(err error, opts ...CaptureOption) { _, event := applyCaptureOptions(opts) event.Exception = []sentry.Exception{ { Type: reflect.TypeOf(err).String(), Value: err.Error(), Stacktrace: sentry.ExtractStacktrace(err), }, } s.hub.CaptureEvent(event) } labkit-v1.7.0/errortracking/tracker.go000066400000000000000000000017741410103036700200150ustar00rootroot00000000000000package errortracking import "github.com/getsentry/sentry-go" // Tracker is an abstraction for error tracking. type Tracker interface { // Capture reports an error to the error reporting service Capture(err error, opts ...CaptureOption) } var ( defaultTracker = newSentryTracker(sentry.CurrentHub()) ) // DefaultTracker returns the default global error tracker. func DefaultTracker() Tracker { return defaultTracker } // NewTracker constructs a new Tracker with the provided options. func NewTracker(opts ...TrackerOption) (Tracker, error) { client, err := sentry.NewClient(trackerOptionsToSentryClientOptions(opts...)) if err != nil { return nil, err } hub := sentry.NewHub(client, sentry.NewScope()) return newSentryTracker(hub), nil } func trackerOptionsToSentryClientOptions(opts ...TrackerOption) sentry.ClientOptions { config := applyTrackerOptions(opts) return sentry.ClientOptions{ Dsn: config.sentryDSN, Release: config.version, Environment: config.sentryEnvironment, } } labkit-v1.7.0/errortracking/tracker_options.go000066400000000000000000000022651410103036700215640ustar00rootroot00000000000000package errortracking // The configuration for Tracker. type trackerConfig struct { sentryDSN string version string sentryEnvironment string loggerName string } // TrackerOption will configure a Tracker. type TrackerOption func(*trackerConfig) func applyTrackerOptions(opts []TrackerOption) trackerConfig { config := trackerConfig{} for _, v := range opts { v(&config) } return config } // WithSentryDSN sets the sentry data source name. func WithSentryDSN(sentryDSN string) TrackerOption { return func(config *trackerConfig) { config.sentryDSN = sentryDSN } } // WithVersion is used to configure the version of the service // that is currently running. func WithVersion(version string) TrackerOption { return func(config *trackerConfig) { config.version = version } } // WithSentryEnvironment sets the sentry environment. func WithSentryEnvironment(sentryEnvironment string) TrackerOption { return func(config *trackerConfig) { config.sentryEnvironment = sentryEnvironment } } // WithLoggerName sets the logger name. func WithLoggerName(loggerName string) TrackerOption { return func(config *trackerConfig) { config.loggerName = loggerName } } labkit-v1.7.0/errortracking/tracker_options_test.go000066400000000000000000000014011410103036700226120ustar00rootroot00000000000000package errortracking import ( "testing" "github.com/stretchr/testify/require" ) func Test_applyTrackerOptions(t *testing.T) { tests := []struct { name string opts []TrackerOption want trackerConfig }{ { name: "with-all-options", opts: []TrackerOption{ WithSentryDSN("dsn"), WithVersion("ver"), WithSentryEnvironment("env"), WithLoggerName("test-logger"), }, want: trackerConfig{ sentryDSN: "dsn", version: "ver", sentryEnvironment: "env", loggerName: "test-logger", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := applyTrackerOptions(tt.opts) require.Equalf(t, got, tt.want, "applyTrackerOptions() = %v, want %v", got, tt.want) }) } } labkit-v1.7.0/example/000077500000000000000000000000001410103036700146015ustar00rootroot00000000000000labkit-v1.7.0/example/router.go000066400000000000000000000034471410103036700164600ustar00rootroot00000000000000package main import ( "fmt" "io" "log" "net/http" "strconv" "time" "gitlab.com/gitlab-org/labkit/correlation" "gitlab.com/gitlab-org/labkit/tracing" tracingcorrelation "gitlab.com/gitlab-org/labkit/tracing/correlation" ) func main() { tracing.Initialize(tracing.WithServiceName("router")) tr := &http.Transport{ MaxIdleConns: 10, IdleConnTimeout: 30 * time.Second, DisableCompression: true, } client := &http.Client{ Transport: correlation.NewInstrumentedRoundTripper(tracing.NewRoundTripper(tr)), } // Listen and propagate traces http.Handle("/query", // Add the tracing middleware in correlation.InjectCorrelationID( tracing.Handler( tracingcorrelation.BaggageHandler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() ttlString := query.Get("ttl") var ttl int var err error if ttlString == "" { ttl = 1 } else { ttl, err = strconv.Atoi(ttlString) if err != nil { ttl = 1 } } ttl-- if ttl < 0 { fmt.Fprintf(w, "Hello") return } nextURL := fmt.Sprintf("http://localhost:8080/query?ttl=%d", ttl) req, err := http.NewRequest(http.MethodGet, nextURL, nil) if err != nil { w.WriteHeader(500) return } req = req.WithContext(r.Context()) resp, err := client.Do(req) if err != nil { w.WriteHeader(500) return } defer resp.Body.Close() _, err = io.Copy(w, resp.Body) if err != nil { w.WriteHeader(500) return } })), // Use this route identifier with the tracing middleware tracing.WithRouteIdentifier("/query"), ), correlation.WithPropagation())) log.Fatal(http.ListenAndServe(":8080", nil)) } labkit-v1.7.0/example/run-datadog-static000077500000000000000000000012631410103036700202230ustar00rootroot00000000000000#!/usr/bin/env bash set -xeuo pipefail IFS=$'\n\t' SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "${SCRIPT_DIR}" docker run -d --name dd-agent \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v /proc/:/host/proc/:ro \ -v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro \ -e "DD_API_KEY=${DD_API_KEY}" \ -e DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true \ -e DD_APM_ENABLED=true \ -p 8125:8125/udp \ -p 8126:8126 \ datadog/agent:latest function finish { docker rm -f dd-agent || true } trap finish EXIT export GO111MODULE=off go build \ -tags "tracer_static tracer_static_datadog" \ router.go GITLAB_TRACING=opentracing://datadog \ ./router labkit-v1.7.0/example/run-jaeger-static000077500000000000000000000012061410103036700200520ustar00rootroot00000000000000#!/usr/bin/env bash set -xeuo pipefail IFS=$'\n\t' SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "${SCRIPT_DIR}" docker run -d --name labkit_jaeger \ -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \ -p 5775:5775/udp \ -p 6831:6831/udp \ -p 6832:6832/udp \ -p 5778:5778 \ -p 16686:16686 \ -p 14268:14268 \ -p 9411:9411 \ jaegertracing/all-in-one:latest function finish { docker rm -f labkit_jaeger || true } trap finish EXIT export GO111MODULE=off go build \ -tags "tracer_static tracer_static_jaeger" \ router.go GITLAB_TRACING="opentracing://jaeger?sampler=const&sampler_param=1" \ ./router labkit-v1.7.0/example/run-no-tracing000077500000000000000000000002761410103036700173770ustar00rootroot00000000000000#!/usr/bin/env bash set -xeuo pipefail IFS=$'\n\t' SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "${SCRIPT_DIR}" export GO111MODULE=off go build router.go ./router labkit-v1.7.0/go.mod000066400000000000000000000024411410103036700142550ustar00rootroot00000000000000module gitlab.com/gitlab-org/labkit require ( cloud.google.com/go v0.81.0 contrib.go.opencensus.io/exporter/stackdriver v0.13.8 github.com/HdrHistogram/hdrhistogram-go v1.1.0 // indirect github.com/Microsoft/go-winio v0.4.19 // indirect github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2 // indirect github.com/client9/reopen v1.0.0 github.com/getsentry/raven-go v0.2.0 github.com/getsentry/sentry-go v0.10.0 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect github.com/lightstep/lightstep-tracer-go v0.24.0 github.com/oklog/ulid/v2 v2.0.2 github.com/opentracing/opentracing-go v1.2.0 github.com/philhofer/fwd v1.0.0 // indirect github.com/prometheus/client_golang v1.10.0 github.com/prometheus/client_model v0.2.0 github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 github.com/uber/jaeger-client-go v2.27.0+incompatible github.com/uber/jaeger-lib v2.4.1+incompatible // indirect go.opencensus.io v0.23.0 google.golang.org/api v0.45.0 google.golang.org/grpc v1.37.0 gopkg.in/DataDog/dd-trace-go.v1 v1.30.0 ) go 1.15 labkit-v1.7.0/go.sum000066400000000000000000003007511410103036700143070ustar00rootroot00000000000000cloud.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= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= contrib.go.opencensus.io/exporter/stackdriver v0.13.8 h1:lIFYmQsqejvlq+GobFUbC5F0prD5gvhP6r0gWLZRDq4= contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk= github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.4.19 h1:ZMZG0O5M8bhD0lgCURV8yu3hQ7TGvQ4L1ZW8+J0j9iE= github.com/Microsoft/go-winio v0.4.19/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= 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/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 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/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 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/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 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/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 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 v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 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/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/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2 h1:MmeatFT1pTPSVb4nkPmBFN/LRZ97vPjsFKsZrU3KKTs= github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= 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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/client9/reopen v1.0.0 h1:8tpLVR74DLpLObrn2KvsyxJY++2iORGR17WLUdSzUws= github.com/client9/reopen v1.0.0/go.mod h1:caXVCEr+lUtoN1FlsRiOWdfQtdRHIYfcb0ai8qKWtkQ= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 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/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 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 v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/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/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/getsentry/sentry-go v0.10.0 h1:6gwY+66NHKqyZrdi6O2jGdo7wGdo9b3B69E01NFgT5g= github.com/getsentry/sentry-go v0.10.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 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-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 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/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 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/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210125172800-10e9aeb4a998/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5 h1:zIaiqGYDQwa4HVx5wGRTXbx38Pqxjemn4BP98wpzpXo= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 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/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= 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/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= 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/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 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.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 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/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 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/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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20200305213919-a88bf8de3718/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.24.0 h1:qGUbkzHP64NA9r+uIbCvf303IzHPr0M4JlkaDMxXqqk= github.com/lightstep/lightstep-tracer-go v0.24.0/go.mod h1:RnONwHKg89zYPmF+Uig5PpHMUcYCFgml8+r4SS53y7A= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 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-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= 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/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= 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-homedir v1.1.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/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 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/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= 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/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 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/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 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/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= 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/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= 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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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/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.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg= github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= 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/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/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y= github.com/prometheus/common v0.18.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/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.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 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 v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 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/ryanuber/columnize v2.1.0+incompatible/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/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a h1:iLcLb5Fwwz7g/DLK89F+uQBDeAhHhwdzB5fSlVdhGcM= github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.20.1+incompatible h1:oIq9Cq4i84Hk8uQAUOG3eNdI/29hBawGrD5YRl6JRDY= github.com/shirou/gopsutil v2.20.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 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/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 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/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 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 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= 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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/uber/jaeger-client-go v2.27.0+incompatible h1:6WVONolFJiB8Vx9bq4z9ddyV/SXSpfvvtb7Yl/TGHiE= github.com/uber/jaeger-client-go v2.27.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 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/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= 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-20181203042331-505ab145d0a9/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-20190605123033-f99c8df09eb5/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-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 h1:rPRtHfUb0UKZeZ6GH4K4Nt4YRbE9V1u+QZX5upZXqJQ= golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20181205085412-a5c9d58dba9a/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/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-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog= golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= 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-20180525024113-a5b4c53f6e8b/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-20181221001348-537d06c36207/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-20190206041539-40960b6deb8e/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-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.37.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.45.0 h1:pqMffJFLBVUDIoYsHcqtxgQVTsmxMDpYLOc5MT4Jrww= google.golang.org/api v0.45.0/go.mod h1:ISLIJCedJolbZvDfAk+Ctuq5hf+aJ33WgtUsfyFoLXA= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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-20190418145605-e7d98fc518a7/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-20190502173448-54afdca5d873/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-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3 h1:K+7Ig5hjiLVA/i1UFUUbCGimWz5/Ey0lAQjT3QiLaPY= google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= 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.21.1/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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/DataDog/dd-trace-go.v1 v1.30.0 h1:yJJrDYzAlUsDPpAVBjv4VFnXKTbgvaJFTX0646xDPi4= gopkg.in/DataDog/dd-trace-go.v1 v1.30.0/go.mod h1:SnKViq44dv/0gjl9RpkP0Y2G3BJSRkp6eYdCSu39iI8= 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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 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/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 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/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.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 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= labkit-v1.7.0/goimports.sh000077500000000000000000000001241410103036700155250ustar00rootroot00000000000000#!/usr/bin/env sh set -euo pipefail find . -name \*.go -exec goimports -w {} \; labkit-v1.7.0/lint.sh000077500000000000000000000006521410103036700144560ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail # Write the code coverage report to gl-code-quality-report.json # and print linting issues to stdout in the format: path/to/file:line description # https://docs.gitlab.com/ee/development/go_guide/#automatic-linting golangci-lint run --timeout 5m --out-format code-climate | tee gl-code-quality-report.json | jq -r '.[] | "\(.location.path):\(.location.lines.begin) \(.description)"' labkit-v1.7.0/log/000077500000000000000000000000001410103036700137275ustar00rootroot00000000000000labkit-v1.7.0/log/access_logger.go000066400000000000000000000112371410103036700170620ustar00rootroot00000000000000package log import ( "bufio" "net" "net/http" "time" "github.com/sebest/xff" "github.com/sirupsen/logrus" "gitlab.com/gitlab-org/labkit/correlation" "gitlab.com/gitlab-org/labkit/mask" ) // AccessLogger will generate access logs for the service. func AccessLogger(h http.Handler, opts ...AccessLoggerOption) http.Handler { config := applyAccessLoggerOptions(opts) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { lrw := newLoggingResponseWriter(w, &config) defer lrw.requestFinished(r) h.ServeHTTP(lrw, r) lrw.setStatus() }) } func newLoggingResponseWriter(rw http.ResponseWriter, config *accessLoggerConfig) notifiableResponseWriter { out := loggingResponseWriter{ rw: rw, started: time.Now(), config: config, } // If the underlying response writer supports hijacking, // we need to ensure that we do too if _, ok := rw.(http.Hijacker); ok { return &hijackingResponseWriter{out} } return &out } // notifiableResponseWriter is a response writer that can be notified when the request is complete, // via the requestFinished method. type notifiableResponseWriter interface { http.ResponseWriter // requestFinished is called by the middleware when the request has completed requestFinished(r *http.Request) setStatus() } type loggingResponseWriter struct { rw http.ResponseWriter status int wroteHeader bool written int64 started time.Time ttfb time.Duration config *accessLoggerConfig contentType string } func (l *loggingResponseWriter) Header() http.Header { return l.rw.Header() } func (l *loggingResponseWriter) Write(data []byte) (int, error) { if !l.wroteHeader { l.WriteHeader(http.StatusOK) } n, err := l.rw.Write(data) l.written += int64(n) return n, err } func (l *loggingResponseWriter) WriteHeader(status int) { if l.wroteHeader { return } l.wroteHeader = true l.status = status l.contentType = l.Header().Get("Content-Type") l.ttfb = time.Since(l.started) l.rw.WriteHeader(status) } func (l *loggingResponseWriter) setStatus() { if !l.wroteHeader { // If upstream never called WriteHeader, the Go net/http server will // respond with status 200 to the client. We should also log status 200 // in that case. l.status = http.StatusOK } } //nolint:cyclop func (l *loggingResponseWriter) accessLogFields(r *http.Request) logrus.Fields { duration := time.Since(l.started) fields := l.config.extraFields(r) fieldsBitMask := l.config.fields // Optionally add built in fields if fieldsBitMask&CorrelationID != 0 { fields[correlation.FieldName] = correlation.ExtractFromContext(r.Context()) } if fieldsBitMask&HTTPHost != 0 { fields[httpHostField] = r.Host } if fieldsBitMask&HTTPRemoteIP != 0 { fields[httpRemoteIPField] = l.getRemoteIP(r) } if fieldsBitMask&HTTPRemoteAddr != 0 { fields[httpRemoteAddrField] = r.RemoteAddr } if fieldsBitMask&HTTPRequestMethod != 0 { fields[httpRequestMethodField] = r.Method } if fieldsBitMask&HTTPURI != 0 { fields[httpURIField] = mask.URL(r.RequestURI) } if fieldsBitMask&HTTPProto != 0 { fields[httpProtoField] = r.Proto } if fieldsBitMask&HTTPResponseStatusCode != 0 { fields[httpResponseStatusCodeField] = l.status } if fieldsBitMask&HTTPResponseSize != 0 { fields[httpResponseSizeField] = l.written } if fieldsBitMask&HTTPRequestReferrer != 0 { fields[httpRequestReferrerField] = mask.URL(r.Referer()) } if fieldsBitMask&HTTPUserAgent != 0 { fields[httpUserAgentField] = r.UserAgent() } if fieldsBitMask&RequestDuration != 0 { fields[requestDurationField] = int64(duration / time.Millisecond) } if fieldsBitMask&RequestTTFB != 0 && l.ttfb > 0 { fields[requestTTFBField] = l.ttfb.Milliseconds() } if fieldsBitMask&System != 0 { fields[systemField] = "http" } if fieldsBitMask&HTTPResponseContentType != 0 { fields[httpResponseContentTypeField] = l.contentType } return fields } func (l *loggingResponseWriter) requestFinished(r *http.Request) { l.config.logger.WithFields(l.accessLogFields(r)).Info("access") } func (l *loggingResponseWriter) getRemoteIP(r *http.Request) string { remoteAddr := xff.GetRemoteAddrIfAllowed(r, l.config.xffAllowed) host, _, err := net.SplitHostPort(remoteAddr) if err != nil { return r.RemoteAddr } return host } // hijackingResponseWriter is like a loggingResponseWriter that supports the http.Hijacker interface. type hijackingResponseWriter struct { loggingResponseWriter } func (l *hijackingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { // The only way to get here is through NewStatsCollectingResponseWriter(), which // checks that this cast will be valid. hijacker := l.rw.(http.Hijacker) return hijacker.Hijack() } labkit-v1.7.0/log/access_logger_fields.go000066400000000000000000000060121410103036700204030ustar00rootroot00000000000000package log // AccessLogField is used to select which fields are recorded in the access log. See WithoutFields. type AccessLogField uint16 const ( // CorrelationID field will record the Correlation-ID in the access log. CorrelationID AccessLogField = 1 << iota // HTTPHost field will record the Host Header in the access log. HTTPHost // HTTPRemoteIP field will record the remote caller in the access log, taking Real-IP and X-Forwarded-For headers into account. HTTPRemoteIP // HTTPRemoteAddr field will record the remote socket endpoint in the access log. HTTPRemoteAddr // HTTPRequestMethod field will record the HTTP method in the access log. HTTPRequestMethod // HTTPURI field will record the URI, including parameters. HTTPURI // HTTPProto field will record the protocol used to make the request in the access log. HTTPProto // HTTPResponseStatusCode field will record the response HTTP status code in the access log. HTTPResponseStatusCode // HTTPResponseSize field will record the response size, in bytes, in the access log. HTTPResponseSize // HTTPRequestReferrer field will record the referer header in the access log. HTTPRequestReferrer // HTTPUserAgent field will record the useragent header in the access log. HTTPUserAgent // RequestDuration field will record the request duration in the access log. RequestDuration // System field will record the system for the log entry. System // HTTPResponseContentType field will record the response content-type in the access log. HTTPResponseContentType // RequestTTFB field will record the time to the first byte being written (HTTP Header effectively) // in the access log. Time is recorded before an actual Write happens to ensure that this metric // is not affected by a slow client receiving data. RequestTTFB ) const defaultEnabledFields = ^AccessLogField(0) // By default, all fields are enabled // For field definitions, consult the Elastic Common Schema field reference // https://www.elastic.co/guide/en/ecs/current/ecs-field-reference.html. const ( httpHostField = "host" // ESC: url.domain httpRemoteIPField = "remote_ip" // ESC: client.ip httpRemoteAddrField = "remote_addr" // ESC: client.address httpRequestMethodField = "method" // ESC: http.request.method httpURIField = "uri" // ESC url.path + `?` + url.query httpProtoField = "proto" // ESC: url.scheme httpResponseStatusCodeField = "status" // ESC: http.response.status_code httpResponseSizeField = "written_bytes" // ESC: http.response.body.bytes httpRequestReferrerField = "referrer" // ESC: http.request.referrer httpUserAgentField = "user_agent" // ESC: user_agent.original requestDurationField = "duration_ms" // ESC: no mapping requestTTFBField = "ttfb_ms" // ESC: no mapping systemField = "system" // ESC: no mapping httpResponseContentTypeField = "content_type" // ESC: no mapping ) labkit-v1.7.0/log/access_logger_options.go000066400000000000000000000037431410103036700206400ustar00rootroot00000000000000package log import ( "net/http" "github.com/sirupsen/logrus" ) // ExtraFieldsGeneratorFunc allows extra fields to be included in the access log. type ExtraFieldsGeneratorFunc func(r *http.Request) Fields // XFFAllowedFunc decides whether X-Forwarded-For headers are to be trusted. type XFFAllowedFunc func(ip string) bool // The configuration for an access logger. type accessLoggerConfig struct { logger *logrus.Logger extraFields ExtraFieldsGeneratorFunc fields AccessLogField xffAllowed XFFAllowedFunc } func nullExtraFieldsGenerator(r *http.Request) Fields { return Fields{} } // AccessLoggerOption will configure a access logger handler. type AccessLoggerOption func(*accessLoggerConfig) func applyAccessLoggerOptions(opts []AccessLoggerOption) accessLoggerConfig { config := accessLoggerConfig{ logger: logger, extraFields: nullExtraFieldsGenerator, fields: defaultEnabledFields, xffAllowed: func(sip string) bool { return true }, } for _, v := range opts { v(&config) } return config } // WithExtraFields allows extra fields to be passed into the access logger, based on the request. func WithExtraFields(f ExtraFieldsGeneratorFunc) AccessLoggerOption { return func(config *accessLoggerConfig) { config.extraFields = f } } // WithFieldsExcluded allows fields to be excluded from the access log. For example, backend services may not require the referer or user agent fields. func WithFieldsExcluded(fields AccessLogField) AccessLoggerOption { return func(config *accessLoggerConfig) { config.fields &^= fields } } // WithAccessLogger configures the logger to be used with the access logger. func WithAccessLogger(logger *logrus.Logger) AccessLoggerOption { return func(config *accessLoggerConfig) { config.logger = logger } } // WithXFFAllowed decides whether to trust X-Forwarded-For headers. func WithXFFAllowed(xffAllowed XFFAllowedFunc) AccessLoggerOption { return func(config *accessLoggerConfig) { config.xffAllowed = xffAllowed } } labkit-v1.7.0/log/access_logger_test.go000066400000000000000000000140421410103036700201160ustar00rootroot00000000000000package log import ( "bytes" "fmt" "io/ioutil" "net/http" "net/http/httptest" "testing" "time" "github.com/stretchr/testify/require" ) func TestAccessLogger(t *testing.T) { tests := []struct { name string urlSuffix string body string logMatchers []string options []AccessLoggerOption requestHeaders map[string]string responseHeaders map[string]string handler http.Handler }{ { name: "trivial", body: "hello", logMatchers: []string{ `\btime=\"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}`, `\blevel=info`, `\bmsg=access`, `\bcorrelation_id=\s+`, `\bduration_ms=\d+`, `\bhost="127.0.0.1:\d+"`, `\bmethod=GET`, `\bproto=HTTP/1.1`, `\breferrer=\s+`, `\bremote_addr="127.0.0.1:\d+"`, `\bremote_ip=127.0.0.1`, `\bstatus=200`, `\bsystem=http`, `\buri=/`, `\buser_agent=Go`, `\bwritten_bytes=5`, `\bcontent_type=\s+`, }, }, { name: "senstitive_params", urlSuffix: "?password=123456", logMatchers: []string{ `\buri=\"/\?password=\[FILTERED\]\"`, }, }, { name: "extra_fields", options: []AccessLoggerOption{ WithExtraFields(func(r *http.Request) Fields { return Fields{"testfield": "testvalue"} }), }, logMatchers: []string{ `\btestfield=testvalue\b`, }, }, { name: "excluded_fields", options: []AccessLoggerOption{ WithFieldsExcluded(defaultEnabledFields), }, logMatchers: []string{ `^time=\"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=info msg=access\n$`, }, }, { name: "x_forwarded_for", requestHeaders: map[string]string{ "X-Forwarded-For": "196.7.0.238", }, logMatchers: []string{ `\bremote_ip=196.7.0.238\b`, }, }, { name: "x_forwarded_for_incorrect", requestHeaders: map[string]string{ "X-Forwarded-For": "gitlab.com", }, logMatchers: []string{ `\bremote_ip=127.0.0.1\b`, }, }, { name: "x_forwarded_for_incorrect", requestHeaders: map[string]string{ "X-Forwarded-For": "196.7.238, 197.7.8.9", }, logMatchers: []string{ `\bremote_ip=197.7.8.9\b`, }, }, { name: "x_forwarded_for_not_allowed", options: []AccessLoggerOption{ WithXFFAllowed(func(sip string) bool { return false }), }, requestHeaders: map[string]string{ "X-Forwarded-For": "196.7.0.238", }, logMatchers: []string{ `\bremote_ip=127.0.0.1\b`, }, }, { name: "empty body", logMatchers: []string{ `\bstatus=200\b`, }, }, { name: "content type", body: "hello", responseHeaders: map[string]string{ "Content-Type": "text/plain", }, logMatchers: []string{ `\bcontent_type=text/plain`, }, }, { name: "time to the first byte", body: "ok", logMatchers: []string{ `\bttfb_ms=\d+`, }, }, { name: "time to the first byte, with long delay", body: "yo", logMatchers: []string{ // we expect the delay to be around `10ms` `\bttfb_ms=1\d\b`, }, handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(10 * time.Millisecond) fmt.Fprint(w, "yo") }), }, { name: "time to the first byte, with a slow data transfer", body: "yo", logMatchers: []string{ // we expect the delay to be lower than `10ms` `\bttfb_ms=\d\b`, }, handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) time.Sleep(10 * time.Millisecond) fmt.Fprint(w, "yo") }), }, { name: "time to the first byte, with a long processing and slow data transfer", body: "yo", logMatchers: []string{ // we expect the delay to be around `10ms` `\bttfb_ms=1\d\b`, }, handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(10 * time.Millisecond) w.WriteHeader(http.StatusInternalServerError) time.Sleep(20 * time.Millisecond) fmt.Fprint(w, "yo") }), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { buf := &bytes.Buffer{} logger := New() _, err := Initialize(WithLogger(logger), WithWriter(buf)) require.NoError(t, err) handler := tt.handler if handler == nil { handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for k, v := range tt.responseHeaders { w.Header().Add(k, v) } // This if-statement provides test coverage for the case where the // handler never calls Write or WriteHeader. if len(tt.body) > 0 { fmt.Fprint(w, tt.body) } }) } opts := []AccessLoggerOption{WithAccessLogger(logger)} opts = append(opts, tt.options...) handler = AccessLogger(handler, opts...) ts := httptest.NewTLSServer(handler) defer ts.Close() client := ts.Client() req, err := http.NewRequest("GET", ts.URL+tt.urlSuffix, nil) require.NoError(t, err) for k, v := range tt.requestHeaders { req.Header.Add(k, v) } res, err := client.Do(req) require.NoError(t, err) gotBody, err := ioutil.ReadAll(res.Body) res.Body.Close() require.NoError(t, err) require.Equal(t, tt.body, string(gotBody)) logString := buf.String() for _, v := range tt.logMatchers { require.Regexp(t, v, logString) } }) } } func TestAccessLoggerPanic(t *testing.T) { buf := &bytes.Buffer{} logger := New() _, err := Initialize(WithLogger(logger), WithWriter(buf)) require.NoError(t, err) var handler http.Handler handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { panic("see how the logger handles a panic") }) opts := []AccessLoggerOption{WithAccessLogger(logger)} handler = AccessLogger(handler, opts...) ts := httptest.NewTLSServer(handler) defer ts.Close() client := ts.Client() req, err := http.NewRequest("GET", ts.URL+"/", nil) require.NoError(t, err) _, err = client.Do(req) require.Error(t, err, "panic should cause the request to fail with a closed connection") require.Regexp(t, `\bstatus=0\b`, buf.String(), "if the handler panics before writing a response header, the status code is undefined, so we expect code 0") } labkit-v1.7.0/log/clock.go000066400000000000000000000007031410103036700153510ustar00rootroot00000000000000package log import ( "time" ) // Clock interface provides the time. type clock interface { Now() time.Time } // realClock is the default time implementation. type realClock struct{} // Now returns the time. func (realClock) Now() time.Time { return time.Now() } // stubClock is the default time implementation. type stubClock struct { StubTime time.Time } // Now returns a stub time. func (c *stubClock) Now() time.Time { return c.StubTime } labkit-v1.7.0/log/combined_formatter.go000066400000000000000000000031211410103036700201160ustar00rootroot00000000000000package log import ( "bytes" "fmt" log "github.com/sirupsen/logrus" ) func forNil(v interface{}, otherwise interface{}) interface{} { if v == nil { return otherwise } return v } // combinedAccessLogFormatter formats logs into a format similar to the combined access log format // See https://httpd.apache.org/docs/1.3/logs.html#combined type combinedAccessLogFormatter struct { clock clock } // Format renders a single log entry. func (f *combinedAccessLogFormatter) Format(entry *log.Entry) ([]byte, error) { host := forNil(entry.Data[httpHostField], "-") remoteAddr := forNil(entry.Data[httpRemoteIPField], "") method := forNil(entry.Data[httpRequestMethodField], "") uri := forNil(entry.Data[httpURIField], "") proto := forNil(entry.Data[httpProtoField], "") status := forNil(entry.Data[httpResponseStatusCodeField], 0) written := forNil(entry.Data[httpResponseSizeField], 0) referer := forNil(entry.Data[httpRequestReferrerField], "") userAgent := forNil(entry.Data[httpUserAgentField], "") duration := forNil(entry.Data[requestDurationField], 0) now := f.clock.Now().Format("2006/01/02:15:04:05 -0700") requestLine := fmt.Sprintf("%s %s %s", method, uri, proto) buf := &bytes.Buffer{} _, err := fmt.Fprintf(buf, "%s %s - - [%s] %q %d %d %q %q %d\n", host, remoteAddr, now, requestLine, status, written, referer, userAgent, duration, ) return buf.Bytes(), err } // newCombinedcombinedAccessLogFormatter returns a new formatter for combined access logs. func newCombinedcombinedAccessLogFormatter() log.Formatter { return &combinedAccessLogFormatter{clock: &realClock{}} } labkit-v1.7.0/log/combined_formatter_test.go000066400000000000000000000026021410103036700211600ustar00rootroot00000000000000package log import ( "io/ioutil" "testing" "time" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) func TestAccessLogFormatter_Format(t *testing.T) { discardLogger := log.New() discardLogger.Out = ioutil.Discard tests := []struct { name string entry *log.Entry want string }{ { "blank", discardLogger.WithField("blank", ""), "- - - [2018/01/07:00:00:00 +0000] \" \" 0 0 \"\" \"\" 0\n", }, { "full", discardLogger.WithFields(log.Fields{ httpHostField: "gitlab.com", httpRemoteIPField: "127.0.0.1", httpRequestMethodField: "GET", httpURIField: "/", httpProtoField: "HTTP/1.1", httpResponseStatusCodeField: 200, httpResponseSizeField: 100, httpRequestReferrerField: "http://localhost", httpUserAgentField: "Mozilla/1.0", requestDurationField: 5, }), "gitlab.com 127.0.0.1 - - [2018/01/07:00:00:00 +0000] \"GET / HTTP/1.1\" 200 100 \"http://localhost\" \"Mozilla/1.0\" 5\n", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f := &combinedAccessLogFormatter{clock: &stubClock{time.Unix(1515283200, 0).UTC()}} got, err := f.Format(tt.entry) if err != nil { t.Errorf("AccessLogFormatter.Format() error = %v", err) return } assert.Equal(t, tt.want, string(got)) }) } } labkit-v1.7.0/log/doc.go000066400000000000000000000007201410103036700150220ustar00rootroot00000000000000/* Package log provides sensible logging defaults for Labkit. Labkit uses Logrus for logging. Applications that use Labkit should rely directly on Labkit. This package provides some utility methods for working with Labkit, including: 1. Initialize Logrus in a consistent manner 1. Using GitLab's defaults with logrus 1. Passing correlation-ids between contexts and logrus Please review the examples for more details of how to use this package. */ package log labkit-v1.7.0/log/examples_test.go000066400000000000000000000031041410103036700171310ustar00rootroot00000000000000package log_test import ( "fmt" "net/http" "gitlab.com/gitlab-org/labkit/log" ) func ExampleInitialize() { // Initialize the global logger closer, err := log.Initialize( log.WithFormatter("json"), // Use JSON formatting log.WithLogLevel("info"), // Use info level log.WithOutputName("stderr"), // Output to stderr ) defer closer.Close() log.WithError(err).Info("This has been logged") // Example of configuring a non-global logger using labkit myLogger := log.New() // New logrus logger closer2, err := log.Initialize( log.WithLogger(myLogger), // Tell logkit to initialize myLogger log.WithFormatter("text"), // Use text formatting log.WithLogLevel("debug"), // Use debug level log.WithOutputName("/var/log/labkit.log"), // Output to `/var/log/labkit.log` ) defer closer2.Close() log.WithError(err).Info("This has been logged") } func ExampleAccessLogger() { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello world") }) // This func is used by WithExtraFields to add additional fields to the logger extraFieldGenerator := func(r *http.Request) log.Fields { return log.Fields{"header": r.Header.Get("X-Magical-Header")} } http.ListenAndServe(":8080", log.AccessLogger(handler, // Add accesslogger middleware log.WithExtraFields(extraFieldGenerator), // Include custom fields into the logs log.WithFieldsExcluded(log.HTTPRequestReferrer|log.HTTPUserAgent), // Exclude user-agent and referrer fields from the logs ), ) } labkit-v1.7.0/log/initialization.go000066400000000000000000000045701410103036700173130ustar00rootroot00000000000000package log import ( "io" "log" "os" "os/signal" "syscall" "github.com/client9/reopen" "github.com/sirupsen/logrus" ) type nopCloser struct{} func (nopCloser) Close() error { return nil } // Initialize will configure the logger based on the options passed. It will // validate the options and if validation fails drop to the defaults while // logging a message to STDERR. func Initialize(opts ...LoggerOption) (io.Closer, error) { conf := applyLoggerOptions(opts) // Being unable to open the output file will cause an error writer, closer, err := getOutputWriter(conf) if err != nil { return closer, err } conf.logger.SetFormatter(conf.buildFormatter()) conf.logger.SetLevel(conf.level) conf.logger.SetOutput(writer) // Only output the warnings _after_ the logger has been configured for _, warning := range conf.warnings { conf.logger.Warn(warning) } return closer, nil } func getOutputWriter(conf *loggerConfig) (io.Writer, io.Closer, error) { if conf.writer != nil { return conf.writer, nopCloser{}, nil } // When writing to a file, use `reopen` so that we can // reopen the file on SIGHUP signals f, err := reopen.NewFileWriterMode(conf.outputPath, 0644) if err != nil { return f, nopCloser{}, err } isMainLogger := conf.logger == logger sighup := make(chan os.Signal, 1) signal.Notify(sighup, syscall.SIGHUP) go listenForSignalHangup(f, isMainLogger, conf.outputPath, sighup) return f, f, nil } // Will listen for SIGHUP signals and reopen the underlying file. func listenForSignalHangup(l reopen.Reopener, isMainLogger bool, logFilePath string, sighup chan os.Signal) { for v := range sighup { // Specifically, do _not_ write to the log that is being reopened, // but log this to the _main_ log file instead as the actual log // might be specialised, eg: an access logger leading to an incorrect entry logger.WithFields(logrus.Fields{"signal": v, "path": logFilePath}).Print("Reopening log file on signal") err := l.Reopen() if err != nil { if isMainLogger { // Main logger failed to reopen, last ditch effort to notify the user, but don't // do this for auxiliary loggers, since we may get double-logs log.Printf("Unable to reopen log file '%s' after %v. Error %v", logFilePath, v, err) } else { logger.WithError(err).WithFields(logrus.Fields{"signal": v, "path": logFilePath}).Print("Failed to reopen log file") } } } } labkit-v1.7.0/log/initialization_test.go000066400000000000000000000114061410103036700203460ustar00rootroot00000000000000package log import ( "bytes" "regexp" "testing" "time" logrus "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" ) func TestInitialize(t *testing.T) { tests := []struct { name string opts []LoggerOption wantErr bool regexps []*regexp.Regexp infoMessage string debugMessage string }{ { name: "trivial", regexps: []*regexp.Regexp{ regexp.MustCompile(`^$`), }, }, { name: "simple", infoMessage: "Hello", regexps: []*regexp.Regexp{ regexp.MustCompile(`^time=.*level=info.*msg=Hello`), }, }, { name: "simple-json", opts: []LoggerOption{ WithFormatter("json"), }, infoMessage: "Hello", regexps: []*regexp.Regexp{ regexp.MustCompile(`^{"level":"info","msg":"Hello"`), }, }, { name: "simple-text", opts: []LoggerOption{ WithFormatter("text"), }, infoMessage: "Hello", regexps: []*regexp.Regexp{ regexp.MustCompile(`^time=.*level=info.*msg=Hello`), }, }, { name: "invalid-formatter", opts: []LoggerOption{ WithFormatter("rubbish"), }, infoMessage: "Hello", regexps: []*regexp.Regexp{ regexp.MustCompile(`^time=.*level=warning.*msg="unknown logging format rubbish, ignoring option"`), regexp.MustCompile(`(?m)^time=.*level=info.*msg=Hello`), }, }, { name: "loglevel-below-threshold", opts: []LoggerOption{ WithLogLevel("info"), }, debugMessage: "Hello", regexps: []*regexp.Regexp{ regexp.MustCompile(`^$`), }, }, { name: "loglevel-at-threshold", opts: []LoggerOption{ WithLogLevel("info"), }, infoMessage: "InfoHello", debugMessage: "DebugHello", regexps: []*regexp.Regexp{ regexp.MustCompile(`^time=.*level=info.*msg=InfoHello\n$`), }, }, { name: "invalid-threshold", opts: []LoggerOption{ WithLogLevel("garbage"), }, infoMessage: "InfoHello", debugMessage: "DebugHello", regexps: []*regexp.Regexp{ regexp.MustCompile(`level=warning msg="unknown log level, ignoring option`), regexp.MustCompile(`(?m)^time=.*level=info.*msg=InfoHello\n$`), }, }, { name: "invalid-threshold-json-format", opts: []LoggerOption{ WithFormatter("json"), WithLogLevel("garbage"), }, infoMessage: "InfoHello", debugMessage: "DebugHello", regexps: []*regexp.Regexp{ regexp.MustCompile(`"msg":"unknown log level, ignoring option: not a valid logrus Level`), regexp.MustCompile(`"msg":"InfoHello"`), }, }, { name: "emit-to-stderr", opts: []LoggerOption{ WithOutputName("stderr"), }, infoMessage: "InfoHello", regexps: []*regexp.Regexp{ regexp.MustCompile(`^time=.*level=info.*msg=InfoHello\n$`), }, }, { name: "emit-to-stdout", opts: []LoggerOption{ WithOutputName("stdout"), }, infoMessage: "InfoHello", regexps: []*regexp.Regexp{ regexp.MustCompile(`^time=.*level=info.*msg=InfoHello\n$`), }, }, { name: "emit-to-file", opts: []LoggerOption{ WithOutputName("/tmp/test.log"), }, infoMessage: "InfoHello", regexps: []*regexp.Regexp{ regexp.MustCompile(`^time=.*level=info.*msg=InfoHello\n$`), }, }, { name: "color", opts: []LoggerOption{ WithFormatter("color"), }, infoMessage: "It's starting to look like a triple rainbow.", regexps: []*regexp.Regexp{ regexp.MustCompile("^\x1b\\[36mINFO\x1b\\[0m\\[0000\\] It's starting to look like a triple rainbow."), }, }, { name: "timezone-utc-plus-12", opts: []LoggerOption{ WithTimezone(time.FixedZone("UTC+12", 12*60*60)), }, infoMessage: "test", regexps: []*regexp.Regexp{ regexp.MustCompile(`^time=[^ ]+\+12:00.*level=info.*msg=test\n$`), }, }, { name: "timezone-utc-plus-zero", opts: []LoggerOption{ WithTimezone(time.UTC), }, infoMessage: "test", regexps: []*regexp.Regexp{ regexp.MustCompile(`^time=[^ ]+Z.*level=info.*msg=test\n$`), }, }, { name: "two-formatters", opts: []LoggerOption{ WithTimezone(time.FixedZone("UTC-12", -12*60*60)), WithFormatter("json"), }, infoMessage: "test", regexps: []*regexp.Regexp{ regexp.MustCompile(`"time":"[^"]+-12:00"`), regexp.MustCompile(`"msg":"test"`), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var buf bytes.Buffer opts := append([]LoggerOption{}, tt.opts...) opts = append(opts, WithWriter(&buf)) closer, err := Initialize(opts...) defer closer.Close() if tt.infoMessage != "" { logrus.Info(tt.infoMessage) } if tt.debugMessage != "" { logrus.Debug(tt.debugMessage) } if tt.wantErr { require.Error(t, err) } else { require.NoError(t, err) } bytes := buf.Bytes() for _, re := range tt.regexps { require.Regexp(t, re, string(bytes)) } }) } } labkit-v1.7.0/log/logger.go000066400000000000000000000020531410103036700155350ustar00rootroot00000000000000package log import ( "context" "os" "github.com/sirupsen/logrus" "gitlab.com/gitlab-org/labkit/correlation" ) var logger = logrus.StandardLogger() func init() { //nolint:gochecknoinits // Enfore our own defaults on the logrus stdlogger logger.Out = os.Stderr logger.Formatter = &logrus.TextFormatter{} logger.Level = logrus.InfoLevel } // ContextLogger will set the correlation id in the logger based on the context. // This reference should not be held outside of the scope of the context. func ContextLogger(ctx context.Context) *logrus.Entry { return logger.WithFields(ContextFields(ctx)) } // WithContextFields is a utility method for logging with context and fields. func WithContextFields(ctx context.Context, fields Fields) *logrus.Entry { return logger.WithFields(ContextFields(ctx)).WithFields(fields) } // ContextFields a logrus fields structure with the CorrelationID set. func ContextFields(ctx context.Context) Fields { correlationID := correlation.ExtractFromContext(ctx) return logrus.Fields{correlation.FieldName: correlationID} } labkit-v1.7.0/log/logger_options.go000066400000000000000000000075401410103036700173160ustar00rootroot00000000000000package log import ( "fmt" "io" "os" "time" "github.com/sirupsen/logrus" ) type loggerConfig struct { logger *logrus.Logger level logrus.Level formatter logrus.Formatter location *time.Location outputPath string writer io.Writer // A list of warnings that will be emitted once the logger is configured warnings []string } type timezoneFormatter struct { formatter logrus.Formatter location *time.Location } // LoggerOption will configure a new logrus Logger. type LoggerOption func(*loggerConfig) func applyLoggerOptions(opts []LoggerOption) *loggerConfig { conf := loggerConfig{ logger: logger, level: logrus.InfoLevel, formatter: &logrus.TextFormatter{}, writer: os.Stdout, } for _, v := range opts { v(&conf) } return &conf } func (l *loggerConfig) buildFormatter() logrus.Formatter { out := l.formatter if l.location != nil { out = &timezoneFormatter{formatter: out, location: l.location} } return out } func (f *timezoneFormatter) Format(entry *logrus.Entry) ([]byte, error) { // Avoid mutating the passed-in entry as callers may retain a reference to // it. Since we don't change the `Data` field, a shallow copy is sufficient. if entry != nil { entryCopy := *entry entryCopy.Time = entryCopy.Time.In(f.location) entry = &entryCopy } return f.formatter.Format(entry) } // WithFormatter allows setting the format to `text`, `json`, `color` or `combined`. In case // the input is not recognized it defaults to text with a warning. // More details of these formats: // * `text` - human readable. // * `json` - computer readable, new-line delimited JSON. // * `color` - human readable, in color. Useful for development. // * `combined` - httpd access logs. Good for legacy access log parsers. func WithFormatter(format string) LoggerOption { return func(conf *loggerConfig) { switch format { case "text": conf.formatter = &logrus.TextFormatter{} case "color": conf.formatter = &logrus.TextFormatter{ForceColors: true, EnvironmentOverrideColors: true} case "json": conf.formatter = &logrus.JSONFormatter{} case "combined": conf.formatter = newCombinedcombinedAccessLogFormatter() default: conf.warnings = append(conf.warnings, fmt.Sprintf("unknown logging format %s, ignoring option", format)) } } } // WithTimezone allows setting the timezone that will be used for log messages. // The default behaviour is to use the local timezone, but a specific timezone // (such as time.UTC) may be preferred. func WithTimezone(location *time.Location) LoggerOption { return func(conf *loggerConfig) { conf.location = location } } // WithLogLevel is used to set the log level when defaulting to `info` is not // wanted. Other options are: `debug`, `warn`, `error`, `fatal`, and `panic`. func WithLogLevel(level string) LoggerOption { return func(conf *loggerConfig) { logrusLevel, err := logrus.ParseLevel(level) if err != nil { conf.warnings = append(conf.warnings, fmt.Sprintf("unknown log level, ignoring option: %v", err)) } else { conf.level = logrusLevel } } } // WithOutputName allows customization of the sink of the logger. Output is either: // `stdout`, `stderr`, or a path to a file. func WithOutputName(outputName string) LoggerOption { return func(conf *loggerConfig) { switch outputName { case "stdout": conf.writer = os.Stdout case "stderr": conf.writer = os.Stderr default: conf.writer = nil conf.outputPath = outputName } } } // WithWriter allows the writer to be customized. The application is responsible for closing the writer manually. func WithWriter(writer io.Writer) LoggerOption { return func(conf *loggerConfig) { conf.writer = writer } } // WithLogger allows you to configure a proprietary logger using the `Initialize` method. func WithLogger(logger *logrus.Logger) LoggerOption { return func(conf *loggerConfig) { conf.logger = logger } } labkit-v1.7.0/log/logger_test.go000066400000000000000000000055711410103036700166040ustar00rootroot00000000000000package log import ( "bytes" "context" "testing" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/labkit/correlation" ) func TestContextLogger(t *testing.T) { tests := []struct { name string correlationID string matchRegExp string }{ { name: "none", correlationID: "", matchRegExp: `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=info msg=Hello correlation_id=\n$`, }, { name: "set", correlationID: "123456", matchRegExp: `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=info msg=Hello correlation_id=123456\n$`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { buf := &bytes.Buffer{} closer, err := Initialize(WithWriter(buf)) require.NoError(t, err) defer closer.Close() ctx := context.Background() if tt.correlationID != "" { ctx = correlation.ContextWithCorrelation(ctx, tt.correlationID) } ContextLogger(ctx).Info("Hello") require.Regexp(t, tt.matchRegExp, buf.String()) }) } } func TestWithContextFields(t *testing.T) { tests := []struct { name string correlationID string fields Fields matchRegExp string }{ { name: "none", correlationID: "", fields: nil, matchRegExp: `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=info msg=Hello correlation_id=\n$`, }, { name: "single", correlationID: "123456", fields: Fields{"field": "value"}, matchRegExp: `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=info msg=Hello correlation_id=123456 field=value\n$`, }, { name: "multiple", correlationID: "123456", fields: Fields{"field": "value", "field2": "value2"}, matchRegExp: `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=info msg=Hello correlation_id=123456 field=value field2=value2\n$`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { buf := &bytes.Buffer{} closer, err := Initialize(WithWriter(buf)) require.NoError(t, err) defer closer.Close() ctx := context.Background() if tt.correlationID != "" { ctx = correlation.ContextWithCorrelation(ctx, tt.correlationID) } WithContextFields(ctx, tt.fields).Info("Hello") require.Regexp(t, tt.matchRegExp, buf.String()) }) } } func TestContextFields(t *testing.T) { tests := []struct { name string correlationID string fields Fields }{ { name: "none", correlationID: "", fields: Fields{correlation.FieldName: ""}, }, { name: "single", correlationID: "123456", fields: Fields{correlation.FieldName: "123456"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() if tt.correlationID != "" { ctx = correlation.ContextWithCorrelation(ctx, tt.correlationID) } got := ContextFields(ctx) require.Equal(t, tt.fields, got) }) } } labkit-v1.7.0/log/logrus.go000066400000000000000000000022521410103036700155720ustar00rootroot00000000000000package log import ( "github.com/sirupsen/logrus" ) // Note that we specifically discourage the use of Fatal, Error by excluding them from the API. // Since we prefer structured logging with `.WithError(err)` // Fields is an alias for the underlying logger Fields type // Using this alias saves clients from having to import // two distinct logging packages, which can be confusing. type Fields = logrus.Fields // New is a delegator method for logrus.New. func New() *logrus.Logger { return logrus.New() } // Info is a delegator method for logrus.Info // Info is an exception to our rule about discouraging non-structured use, as there are valid // reasons for simply emitting a single log line. func Info(args ...interface{}) { logger.Info(args...) } // WithField is a delegator method for logrus.WithField. func WithField(key string, value interface{}) *logrus.Entry { return logger.WithField(key, value) } // WithFields is a delegator method for logrus.WithFields. func WithFields(fields Fields) *logrus.Entry { return logger.WithFields(fields) } // WithError is a delegator method for logrus.WithError. func WithError(err error) *logrus.Entry { return logger.WithError(err) } labkit-v1.7.0/log/logrus_test.go000066400000000000000000000030331410103036700166270ustar00rootroot00000000000000package log import ( "bytes" "fmt" "testing" "github.com/stretchr/testify/require" ) func TestNew(t *testing.T) { buf := &bytes.Buffer{} logger := New() logger.SetOutput(buf) logger.Info("Hello") require.Regexp(t, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=info msg=Hello\n$`, buf.String()) } func TestInfo(t *testing.T) { buf := &bytes.Buffer{} closer, err := Initialize(WithWriter(buf)) require.NoError(t, err) defer closer.Close() Info("Information") require.Regexp(t, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=info msg=Information\n$`, buf.String()) } func TestWithField(t *testing.T) { buf := &bytes.Buffer{} closer, err := Initialize(WithWriter(buf)) require.NoError(t, err) defer closer.Close() WithField("field", "value").Warn("notice") require.Regexp(t, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=warning msg=notice field=value\n$`, buf.String()) } func TestWithFields(t *testing.T) { buf := &bytes.Buffer{} closer, err := Initialize(WithWriter(buf)) require.NoError(t, err) defer closer.Close() WithFields(Fields{"field": "value", "field2": "value2"}).Warn("another") require.Regexp(t, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=warning msg=another field=value field2=value2\n$`, buf.String()) } func TestWithError(t *testing.T) { buf := &bytes.Buffer{} closer, err := Initialize(WithWriter(buf)) require.NoError(t, err) defer closer.Close() WithError(fmt.Errorf("fail")).Error("rejected") require.Regexp(t, `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.*level=error msg=rejected error=fail\n$`, buf.String()) } labkit-v1.7.0/mask/000077500000000000000000000000001410103036700141015ustar00rootroot00000000000000labkit-v1.7.0/mask/consts.go000066400000000000000000000002311410103036700157350ustar00rootroot00000000000000package mask // RedactionString represents the filtered value used in place of sensitive data in the mask package. const RedactionString = "[FILTERED]" labkit-v1.7.0/mask/doc.go000066400000000000000000000006211410103036700151740ustar00rootroot00000000000000/* Package mask offers the functionality for non-reversible masking of sensitive data in the GitLab application. It provides the ability to determine whether or not a URL parameter or Header should be considered sensitive. Additionally, it provides masking functionality to mask sensitive information that gets logged via logging, structured logging, sentry and distributed tracing. */ package mask labkit-v1.7.0/mask/matchers.go000066400000000000000000000022671410103036700162450ustar00rootroot00000000000000package mask import ( "bytes" "regexp" ) var parameterMatches = []string{ `token$`, `password`, `secret`, `key$`, `signature`, `^authorization$`, `^certificate$`, `^encrypted_key$`, `^hook$`, `^import_url$`, `^elasticsearch_url$`, `^otp_attempt$`, `^sentry_dsn$`, `^trace$`, `^variables$`, `^content$`, `^body$`, `^description$`, `^note$`, `^text$`, `^title$`, `^jwt$`, } var headerMatches = []string{ `token$`, `password`, `secret`, `key$`, `signature`, `^authorization$`, } // parameterMatcher is precompiled for performance reasons. Keep in mind that // `IsSensitiveParam`, `IsSensitiveHeader` and `URL` may be used in tight loops // which may be sensitive to performance degradations. var parameterMatcher = compileRegexpFromStrings(parameterMatches) // headerMatcher is precompiled for performance reasons, same as `parameterMatcher`. var headerMatcher = compileRegexpFromStrings(headerMatches) func compileRegexpFromStrings(paramNames []string) *regexp.Regexp { var buffer bytes.Buffer buffer.WriteString("(?i)") for i, v := range paramNames { if i > 0 { buffer.WriteString("|") } buffer.WriteString(v) } return regexp.MustCompile(buffer.String()) } labkit-v1.7.0/mask/sensitive.go000066400000000000000000000006021410103036700164370ustar00rootroot00000000000000package mask // IsSensitiveParam will return true if the given parameter name should be masked for sensitivity. func IsSensitiveParam(name string) bool { return parameterMatcher.MatchString(name) } // IsSensitiveHeader will return true if the given parameter name should be masked for sensitivity. func IsSensitiveHeader(name string) bool { return headerMatcher.MatchString(name) } labkit-v1.7.0/mask/sensitive_test.go000066400000000000000000000052551410103036700175070ustar00rootroot00000000000000package mask import ( "strings" "testing" "github.com/stretchr/testify/require" ) type matches uint8 const ( whole matches = 1 << iota inMiddle atStart atEnd ) const allMatches matches = whole ^ inMiddle ^ atStart ^ atEnd const noMatches matches = 0 func TestIsSensitiveParam(t *testing.T) { tests := []struct { name string want matches }{ {"token", whole ^ atEnd}, {"password", allMatches}, {"secret", allMatches}, {"key", whole ^ atEnd}, {"signature", allMatches}, {"authorization", whole}, {"certificate", whole}, {"encrypted_key", whole ^ atEnd}, {"hook", whole}, {"import_url", whole}, {"elasticsearch_url", whole}, {"otp_attempt", whole}, {"sentry_dsn", whole}, {"trace", whole}, {"variables", whole}, {"content", whole}, {"body", whole}, {"description", whole}, {"note", whole}, {"text", whole}, {"title", whole}, {"gitlab", noMatches}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { check := func(v string) { // Normal case gotWhole := IsSensitiveParam(v) require.Equal(t, tt.want&whole != 0, gotWhole, "Whole param match on '%s'", v) gotMiddle := IsSensitiveParam("prefix" + v + "suffix") require.Equal(t, tt.want&inMiddle != 0, gotMiddle, "Middle match on '%s'", "prefix"+v+"suffix") gotStart := IsSensitiveParam(v + "suffix") require.Equal(t, tt.want&atStart != 0, gotStart, "Start match on '%s'", v+"suffix") gotEnd := IsSensitiveParam("prefix" + v) require.Equal(t, tt.want&atEnd != 0, gotEnd, "End match on '%s'", "prefix"+v) } check(tt.name) check(strings.ToUpper(tt.name)) check(strings.ToLower(tt.name)) }) } } func TestIsSensitiveHeader(t *testing.T) { tests := []struct { name string want matches }{ {"token", whole ^ atEnd}, {"password", allMatches}, {"secret", allMatches}, {"key", whole ^ atEnd}, {"signature", allMatches}, {"authorization", whole}, {"name", noMatches}, {"gitlab", noMatches}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { check := func(v string) { gotWhole := IsSensitiveHeader(v) require.Equal(t, tt.want&whole != 0, gotWhole, "Whole param match on '%s'", v) gotMiddle := IsSensitiveHeader("prefix" + v + "suffix") require.Equal(t, tt.want&inMiddle != 0, gotMiddle, "Middle match on '%s'", "prefix"+v+"suffix") gotStart := IsSensitiveHeader(v + "suffix") require.Equal(t, tt.want&atStart != 0, gotStart, "Start match on '%s'", v+"suffix") gotEnd := IsSensitiveHeader("prefix" + v) require.Equal(t, tt.want&atEnd != 0, gotEnd, "End match on '%s'", "prefix"+v) } check(tt.name) check(strings.ToUpper(tt.name)) check(strings.ToLower(tt.name)) }) } } labkit-v1.7.0/mask/url.go000066400000000000000000000017661410103036700152440ustar00rootroot00000000000000package mask import ( "bytes" "net/url" ) // URL will mask the sensitive components in an URL with `[FILTERED]`. // This list should maintain parity with the list in // GitLab-CE, maintained at https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/application.rb. // Based on https://stackoverflow.com/a/52965552/474597. func URL(originalURL string) string { u, err := url.Parse(originalURL) if err != nil { return "" } redactionBytes := []byte(RedactionString) buf := bytes.NewBuffer(make([]byte, 0, len(originalURL))) for i, queryPart := range bytes.Split([]byte(u.RawQuery), []byte("&")) { if i != 0 { buf.WriteByte('&') } splitParam := bytes.SplitN(queryPart, []byte("="), 2) if len(splitParam) == 2 { buf.Write(splitParam[0]) buf.WriteByte('=') if parameterMatcher.Match(splitParam[0]) { buf.Write(redactionBytes) } else { buf.Write(splitParam[1]) } } else { buf.Write(queryPart) } } u.RawQuery = buf.String() return u.String() } labkit-v1.7.0/mask/url_test.go000066400000000000000000000127401410103036700162750ustar00rootroot00000000000000package mask import ( "testing" "github.com/stretchr/testify/require" ) func BenchmarkURL(b *testing.B) { for n := 0; n < b.N; n++ { URL(`http://localhost:8000?token=123&something_else=92384&secret=sdmalaksjdasd&hook=123901283019238&trace=12312312312123`) } } func TestURL(t *testing.T) { tests := map[string]string{ "http://localhost:8000": "http://localhost:8000", "https://gitlab.com/": "https://gitlab.com/", "custom://gitlab.com?secret=x": "custom://gitlab.com?secret=[FILTERED]", "gitlab.com?secret=x": "gitlab.com?secret=[FILTERED]", ":": "", "http://example.com": "http://example.com", "http://example.com?foo=1": "http://example.com?foo=1", "http://example.com?foo=token": "http://example.com?foo=token", "http://example.com?title=token": "http://example.com?title=[FILTERED]", "http://example.com?authenticity_token=1": "http://example.com?authenticity_token=[FILTERED]", "http://example.com?private_token=1": "http://example.com?private_token=[FILTERED]", "http://example.com?rss_token=1": "http://example.com?rss_token=[FILTERED]", "http://example.com?access_token=1": "http://example.com?access_token=[FILTERED]", "http://example.com?refresh_token=1": "http://example.com?refresh_token=[FILTERED]", "http://example.com?foo&authenticity_token=blahblah&bar": "http://example.com?foo&authenticity_token=[FILTERED]&bar", "http://example.com?private-token=1": "http://example.com?private-token=[FILTERED]", "http://example.com?foo&private-token=blahblah&bar": "http://example.com?foo&private-token=[FILTERED]&bar", "http://example.com?private-token=foo&authenticity_token=bar": "http://example.com?private-token=[FILTERED]&authenticity_token=[FILTERED]", "https://example.com:8080?private-token=foo&authenticity_token=bar": "https://example.com:8080?private-token=[FILTERED]&authenticity_token=[FILTERED]", "/?private-token=foo&authenticity_token=bar": "/?private-token=[FILTERED]&authenticity_token=[FILTERED]", "?private-token=&authenticity_token=&bar": "?private-token=[FILTERED]&authenticity_token=[FILTERED]&bar", "?private-token=foo&authenticity_token=bar": "?private-token=[FILTERED]&authenticity_token=[FILTERED]", "?private_token=foo&authenticity-token=bar": "?private_token=[FILTERED]&authenticity-token=[FILTERED]", "?X-AMZ-Signature=foo": "?X-AMZ-Signature=[FILTERED]", "?x-amz-signature=foo": "?x-amz-signature=[FILTERED]", "?Signature=foo": "?Signature=[FILTERED]", "?confirmation_password=foo": "?confirmation_password=[FILTERED]", "?pos_secret_number=foo": "?pos_secret_number=[FILTERED]", "?sharedSecret=foo": "?sharedSecret=[FILTERED]", "?book_key=foo": "?book_key=[FILTERED]", "?certificate=foo": "?certificate=[FILTERED]", "?hook=foo": "?hook=[FILTERED]", "?import_url=foo": "?import_url=[FILTERED]", "?elasticsearch_url=foo": "?elasticsearch_url=[FILTERED]", "?otp_attempt=foo": "?otp_attempt=[FILTERED]", "?sentry_dsn=foo": "?sentry_dsn=[FILTERED]", "?trace=foo": "?trace=[FILTERED]", "?variables=foo": "?variables=[FILTERED]", "?content=foo": "?content=[FILTERED]", "?content=e=mc2": "?content=[FILTERED]", "?formula=e=mc2": "?formula=e=mc2", "http://%41:8080/": "", "https://gitlab.com?name=andrew&password=1&secret=1&key=1&signature=1&authorization=1¬e=1&certificate=1&encrypted_key=1&hook=1&import_url=1&otp_attempt=1&sentry_dsn=1&trace=1&variables=1&content=1&sharedsecret=1&real=1": "https://gitlab.com?name=andrew&password=[FILTERED]&secret=[FILTERED]&key=[FILTERED]&signature=[FILTERED]&authorization=[FILTERED]¬e=[FILTERED]&certificate=[FILTERED]&encrypted_key=[FILTERED]&hook=[FILTERED]&import_url=[FILTERED]&otp_attempt=[FILTERED]&sentry_dsn=[FILTERED]&trace=[FILTERED]&variables=[FILTERED]&content=[FILTERED]&sharedsecret=[FILTERED]&real=1", // nolint:lll } for url, want := range tests { t.Run(url, func(t *testing.T) { got := URL(url) require.Equal(t, want, got) }) } } labkit-v1.7.0/metrics/000077500000000000000000000000001410103036700146145ustar00rootroot00000000000000labkit-v1.7.0/metrics/doc.go000066400000000000000000000003571410103036700157150ustar00rootroot00000000000000/* Package metrics is the primary entrypoint into LabKit's metrics utilities. Provided Functionality - Provides http middlewares for recording metrics for an http server - Provides a collector for sql.DBStats metrics */ package metrics labkit-v1.7.0/metrics/examples_test.go000066400000000000000000000015741410103036700200270ustar00rootroot00000000000000package metrics_test import ( "fmt" "log" "net/http" "gitlab.com/gitlab-org/labkit/metrics" ) func ExampleNewHandlerFactory() { // Tell prometheus to include a "route" label for the http metrics newMetricHandlerFunc := metrics.NewHandlerFactory(metrics.WithLabels("route")) http.Handle("/foo", newMetricHandlerFunc( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello foo") }), metrics.WithLabelValues(map[string]string{"route": "/foo"}), // Add instrumentation with a custom label of route="/foo" )) http.Handle("/bar", newMetricHandlerFunc( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello bar") }), metrics.WithLabelValues(map[string]string{"route": "/bar"}), // Add instrumentation with a custom label of route="/bar" )) log.Fatal(http.ListenAndServe(":8080", nil)) } labkit-v1.7.0/metrics/handler.go000066400000000000000000000106631410103036700165660ustar00rootroot00000000000000package metrics import ( "net/http" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) // Metric names for the recorded metrics. // These are the conventional names prometheus uses for these metrics. const ( httpInFlightRequestsMetricName = "in_flight_requests" httpRequestsTotalMetricName = "requests_total" httpRequestDurationSecondsMetricName = "request_duration_seconds" httpRequestSizeBytesMetricName = "request_size_bytes" httpResponseSizeBytesMetricName = "response_size_bytes" httpTimeToWriteHeaderSecondsMetricName = "time_to_write_header_seconds" ) // HandlerFactory creates handler middleware instances. Created by NewHandlerFactory. type HandlerFactory func(next http.Handler, opts ...HandlerOption) http.Handler // NewHandlerFactory will create a function for creating metric middlewares. // The resulting function can be called multiple times to obtain multiple middleware // instances. // Each instance can be configured with different options that will be applied to the // same underlying metrics. func NewHandlerFactory(opts ...HandlerFactoryOption) HandlerFactory { factoryConfig := applyHandlerFactoryOptions(opts) var ( httpInFlightRequests = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: factoryConfig.namespace, Subsystem: factoryConfig.subsystem, Name: httpInFlightRequestsMetricName, Help: "A gauge of requests currently being served by the http server.", }) httpRequestsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: factoryConfig.namespace, Subsystem: factoryConfig.subsystem, Name: httpRequestsTotalMetricName, Help: "A counter for requests to the http server.", }, factoryConfig.labels, ) httpRequestDurationSeconds = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: factoryConfig.namespace, Subsystem: factoryConfig.subsystem, Name: httpRequestDurationSecondsMetricName, Help: "A histogram of latencies for requests to the http server.", Buckets: factoryConfig.requestDurationBuckets, }, factoryConfig.labels, ) httpRequestSizeBytes = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: factoryConfig.namespace, Subsystem: factoryConfig.subsystem, Name: httpRequestSizeBytesMetricName, Help: "A histogram of sizes of requests to the http server.", Buckets: factoryConfig.byteSizeBuckets, }, factoryConfig.labels, ) httpResponseSizeBytes = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: factoryConfig.namespace, Subsystem: factoryConfig.subsystem, Name: httpResponseSizeBytesMetricName, Help: "A histogram of response sizes for requests to the http server.", Buckets: factoryConfig.byteSizeBuckets, }, factoryConfig.labels, ) httpTimeToWriteHeaderSeconds = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: factoryConfig.namespace, Subsystem: factoryConfig.subsystem, Name: httpTimeToWriteHeaderSecondsMetricName, Help: "A histogram of request durations until the response headers are written.", Buckets: factoryConfig.timeToWriteHeaderDurationBuckets, }, factoryConfig.labels, ) ) prometheus.MustRegister(httpInFlightRequests) prometheus.MustRegister(httpRequestsTotal) prometheus.MustRegister(httpRequestDurationSeconds) prometheus.MustRegister(httpRequestSizeBytes) prometheus.MustRegister(httpResponseSizeBytes) prometheus.MustRegister(httpTimeToWriteHeaderSeconds) return func(next http.Handler, handlerOpts ...HandlerOption) http.Handler { handlerConfig := applyHandlerOptions(handlerOpts) handler := next handler = promhttp.InstrumentHandlerCounter(httpRequestsTotal.MustCurryWith(handlerConfig.labelValues), handler) handler = promhttp.InstrumentHandlerDuration(httpRequestDurationSeconds.MustCurryWith(handlerConfig.labelValues), handler) handler = promhttp.InstrumentHandlerInFlight(httpInFlightRequests, handler) handler = promhttp.InstrumentHandlerRequestSize(httpRequestSizeBytes.MustCurryWith(handlerConfig.labelValues), handler) handler = promhttp.InstrumentHandlerResponseSize(httpResponseSizeBytes.MustCurryWith(handlerConfig.labelValues), handler) handler = promhttp.InstrumentHandlerTimeToWriteHeader(httpTimeToWriteHeaderSeconds.MustCurryWith(handlerConfig.labelValues), handler) return handler } } labkit-v1.7.0/metrics/handler_factory_options.go000066400000000000000000000050511410103036700220630ustar00rootroot00000000000000package metrics type handlerFactoryConfig struct { namespace string subsystem string requestDurationBuckets []float64 timeToWriteHeaderDurationBuckets []float64 byteSizeBuckets []float64 labels []string } // HandlerFactoryOption is used to pass options in NewHandlerFactory. type HandlerFactoryOption func(*handlerFactoryConfig) func applyHandlerFactoryOptions(opts []HandlerFactoryOption) handlerFactoryConfig { config := handlerFactoryConfig{ subsystem: "http", requestDurationBuckets: []float64{ 0.005, /* 5ms */ 0.025, /* 25ms */ 0.1, /* 100ms */ 0.5, /* 500ms */ 1.0, /* 1s */ 10.0, /* 10s */ 30.0, /* 30s */ 60.0, /* 1m */ 300.0, /* 5m */ }, timeToWriteHeaderDurationBuckets: []float64{ 0.005, /* 5ms */ 0.025, /* 25ms */ 0.1, /* 100ms */ 0.5, /* 500ms */ 1.0, /* 1s */ 10.0, /* 10s */ 30.0, /* 30s */ }, byteSizeBuckets: []float64{ 10, 64, 256, 1024, /* 1KiB */ 64 * 1024, /* 64KiB */ 256 * 1024, /* 256KiB */ 1024 * 1024, /* 1MiB */ 64 * 1024 * 1024, /* 64MiB */ }, labels: []string{"code", "method"}, } for _, v := range opts { v(&config) } return config } // WithNamespace will configure the namespace to apply to the metrics. func WithNamespace(namespace string) HandlerFactoryOption { return func(config *handlerFactoryConfig) { config.namespace = namespace } } // WithLabels will configure additional labels to apply to the metrics. func WithLabels(labels ...string) HandlerFactoryOption { return func(config *handlerFactoryConfig) { config.labels = append(config.labels, labels...) } } // WithRequestDurationBuckets will configure the duration buckets used for // incoming request histogram buckets. func WithRequestDurationBuckets(buckets []float64) HandlerFactoryOption { return func(config *handlerFactoryConfig) { config.requestDurationBuckets = buckets } } // WithTimeToWriteHeaderDurationBuckets will configure the time to write header // duration histogram buckets. func WithTimeToWriteHeaderDurationBuckets(buckets []float64) HandlerFactoryOption { return func(config *handlerFactoryConfig) { config.timeToWriteHeaderDurationBuckets = buckets } } // WithByteSizeBuckets will configure the byte size histogram buckets for request // and response payloads. func WithByteSizeBuckets(buckets []float64) HandlerFactoryOption { return func(config *handlerFactoryConfig) { config.byteSizeBuckets = buckets } } labkit-v1.7.0/metrics/handler_options.go000066400000000000000000000010641410103036700203340ustar00rootroot00000000000000package metrics type handlerConfig struct { labelValues map[string]string } // HandlerOption is used to pass options to the HandlerFactory instance. type HandlerOption func(*handlerConfig) func applyHandlerOptions(opts []HandlerOption) handlerConfig { config := handlerConfig{} for _, v := range opts { v(&config) } return config } // WithLabelValues will configure labels values to apply to this handler. func WithLabelValues(labelValues map[string]string) HandlerOption { return func(config *handlerConfig) { config.labelValues = labelValues } } labkit-v1.7.0/metrics/handler_test.go000066400000000000000000000075321410103036700176260ustar00rootroot00000000000000package metrics import ( "fmt" "net/http" "net/http/httptest" "testing" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" ) func TestNewHandlerFactory(t *testing.T) { tests := []struct { name string opts []HandlerFactoryOption handlerOpts []HandlerOption }{ { name: "basic", opts: []HandlerFactoryOption{ WithNamespace("basic"), }, }, { name: "labels", opts: []HandlerFactoryOption{ WithNamespace("labels"), WithLabels("label1", "label2"), }, handlerOpts: []HandlerOption{ WithLabelValues(map[string]string{"label1": "1", "label2": "1"}), }, }, { name: "request_duration_buckets", opts: []HandlerFactoryOption{ WithNamespace("request_duration_buckets"), WithRequestDurationBuckets([]float64{1, 2, 3}), }, }, { name: "time_to_write_header_duration_buckets", opts: []HandlerFactoryOption{ WithNamespace("time_to_write_header_duration_buckets"), WithTimeToWriteHeaderDurationBuckets([]float64{1, 2, 3}), }, }, { name: "byte_bucket_size_buckets", opts: []HandlerFactoryOption{ WithNamespace("byte_bucket_size_buckets"), WithByteSizeBuckets([]float64{1, 2, 3}), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := NewHandlerFactory(tt.opts...) require.NotNil(t, got) var invoked bool handler := got(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { invoked = true w.WriteHeader(200) fmt.Fprint(w, "OK") }), tt.handlerOpts...) r := httptest.NewRequest("GET", "http://example.com", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) metrics, err := prometheus.DefaultGatherer.Gather() require.NoError(t, err) require.NotEmpty(t, metrics) keyedMetrics := make(map[string]*dto.MetricFamily, len(metrics)) for _, v := range metrics { keyedMetrics[*v.Name] = v } httpInFlightRequestsMetric := keyedMetrics[tt.name+"_http_"+httpInFlightRequestsMetricName] require.NotNil(t, httpInFlightRequestsMetric, "No metric named %v", tt.name+"_http_"+httpInFlightRequestsMetricName) require.Equal(t, 0.0, *httpInFlightRequestsMetric.Metric[0].Gauge.Value) httpRequestsTotalMetric := keyedMetrics[tt.name+"_http_"+httpRequestsTotalMetricName] require.NotNil(t, httpRequestsTotalMetric, "No metric named %v", tt.name+"_http_"+httpRequestsTotalMetricName) require.Equal(t, 1.0, *httpRequestsTotalMetric.Metric[0].Counter.Value) httpRequestDurationSecondsMetric := keyedMetrics[tt.name+"_http_"+httpRequestDurationSecondsMetricName] require.NotNil(t, httpRequestDurationSecondsMetric, "No metric named %v", tt.name+"_http_"+httpRequestDurationSecondsMetricName) require.Equal(t, uint64(1), *httpRequestDurationSecondsMetric.Metric[0].Histogram.SampleCount) httpRequestSizeBytesMetric := keyedMetrics[tt.name+"_http_"+httpRequestSizeBytesMetricName] require.NotNil(t, httpRequestSizeBytesMetric, "No metric named %v", tt.name+"_http_"+httpRequestSizeBytesMetricName) require.Equal(t, uint64(1), *httpRequestSizeBytesMetric.Metric[0].Histogram.SampleCount) httpResponseSizeBytesMetric := keyedMetrics[tt.name+"_http_"+httpResponseSizeBytesMetricName] require.NotNil(t, httpResponseSizeBytesMetric, "No metric named %v", tt.name+"_http_"+httpResponseSizeBytesMetricName) require.Equal(t, uint64(1), *httpResponseSizeBytesMetric.Metric[0].Histogram.SampleCount) httpTimeToWriteHeaderSecondsMetric := keyedMetrics[tt.name+"_http_"+httpTimeToWriteHeaderSecondsMetricName] require.NotNil(t, httpTimeToWriteHeaderSecondsMetric, "No metric named %v", tt.name+"_http_"+httpTimeToWriteHeaderSecondsMetricName) require.Equal(t, uint64(1), *httpTimeToWriteHeaderSecondsMetric.Metric[0].Histogram.SampleCount) require.True(t, invoked, "handler not executed") }) } } labkit-v1.7.0/metrics/sqlmetrics/000077500000000000000000000000001410103036700170025ustar00rootroot00000000000000labkit-v1.7.0/metrics/sqlmetrics/dbstats.go000066400000000000000000000127261410103036700210050ustar00rootroot00000000000000package sqlmetrics import ( "database/sql" "github.com/prometheus/client_golang/prometheus" ) const ( namespace = "go_sql_dbstats" subsystem = "connections" dbNameLabel = "db_name" // Names for the recorded metrics. maxOpenConnectionsName = "max_open" openConnectionsName = "open" inUseName = "in_use" idleName = "idle" waitCountName = "waits_total" waitDurationName = "wait_seconds_total" maxIdleClosedName = "max_idle_closed_count_total" maxLifetimeClosedName = "max_lifetime_closed_count_total" // Descriptions for the recorded metrics. maxOpenConnectionsDesc = "The limit of open connections to the database." openConnectionsDesc = "The number of established connections both in use and idle." inUseDesc = "The number of connections currently in use." idleDesc = "The number of idle connections." waitCountDesc = "The total number of connections waited for." waitDurationDesc = "The total time blocked waiting for a new connection." maxIdleClosedDesc = "The total number of connections closed due to SetMaxIdleConns." maxLifetimeClosedDesc = "The total number of connections closed due to SetConnMaxLifetime." ) // DBStatsGetter is an interface for sql.DBStats. It's implemented by sql.DB. type DBStatsGetter interface { Stats() sql.DBStats } // DBStatsCollector implements the prometheus.Collector interface. type DBStatsCollector struct { sg DBStatsGetter maxOpenDesc *prometheus.Desc openDesc *prometheus.Desc inUseDesc *prometheus.Desc idleDesc *prometheus.Desc waitCountDesc *prometheus.Desc waitDurationDesc *prometheus.Desc maxIdleClosedDesc *prometheus.Desc maxLifetimeClosedDesc *prometheus.Desc } // Describe implements the prometheus.Collector interface. func (c *DBStatsCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.maxOpenDesc ch <- c.openDesc ch <- c.inUseDesc ch <- c.idleDesc ch <- c.waitCountDesc ch <- c.waitDurationDesc ch <- c.maxIdleClosedDesc ch <- c.maxLifetimeClosedDesc } // Collect implements the prometheus.Collector interface. func (c *DBStatsCollector) Collect(ch chan<- prometheus.Metric) { stats := c.sg.Stats() ch <- prometheus.MustNewConstMetric( c.maxOpenDesc, prometheus.GaugeValue, float64(stats.MaxOpenConnections), ) ch <- prometheus.MustNewConstMetric( c.openDesc, prometheus.GaugeValue, float64(stats.OpenConnections), ) ch <- prometheus.MustNewConstMetric( c.inUseDesc, prometheus.GaugeValue, float64(stats.InUse), ) ch <- prometheus.MustNewConstMetric( c.idleDesc, prometheus.GaugeValue, float64(stats.Idle), ) ch <- prometheus.MustNewConstMetric( c.waitCountDesc, prometheus.CounterValue, float64(stats.WaitCount), ) ch <- prometheus.MustNewConstMetric( c.waitDurationDesc, prometheus.CounterValue, stats.WaitDuration.Seconds(), ) ch <- prometheus.MustNewConstMetric( c.maxIdleClosedDesc, prometheus.CounterValue, float64(stats.MaxIdleClosed), ) ch <- prometheus.MustNewConstMetric( c.maxLifetimeClosedDesc, prometheus.CounterValue, float64(stats.MaxLifetimeClosed), ) } type dbStatsCollectorConfig struct { extraLabels prometheus.Labels } // DBStatsCollectorOption is used to pass options in NewDBStatsCollector. type DBStatsCollectorOption func(*dbStatsCollectorConfig) // WithExtraLabels will configure extra label values to apply to the DBStats metrics. // A label named db_name will be ignored, as this is set internally. func WithExtraLabels(labelValues map[string]string) DBStatsCollectorOption { return func(config *dbStatsCollectorConfig) { config.extraLabels = labelValues } } func applyDBStatsCollectorOptions(opts []DBStatsCollectorOption) dbStatsCollectorConfig { config := dbStatsCollectorConfig{} for _, v := range opts { v(&config) } return config } // NewDBStatsCollector creates a new DBStatsCollector. func NewDBStatsCollector(dbName string, sg DBStatsGetter, opts ...DBStatsCollectorOption) *DBStatsCollector { config := applyDBStatsCollectorOptions(opts) if config.extraLabels == nil { config.extraLabels = make(prometheus.Labels) } config.extraLabels[dbNameLabel] = dbName return &DBStatsCollector{ sg: sg, maxOpenDesc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, maxOpenConnectionsName), maxOpenConnectionsDesc, nil, config.extraLabels, ), openDesc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, openConnectionsName), openConnectionsDesc, nil, config.extraLabels, ), inUseDesc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, inUseName), inUseDesc, nil, config.extraLabels, ), idleDesc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, idleName), idleDesc, nil, config.extraLabels, ), waitCountDesc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, waitCountName), waitCountDesc, nil, config.extraLabels, ), waitDurationDesc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, waitDurationName), waitDurationDesc, nil, config.extraLabels, ), maxIdleClosedDesc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, maxIdleClosedName), maxIdleClosedDesc, nil, config.extraLabels, ), maxLifetimeClosedDesc: prometheus.NewDesc( prometheus.BuildFQName(namespace, subsystem, maxLifetimeClosedName), maxLifetimeClosedDesc, nil, config.extraLabels, ), } } labkit-v1.7.0/metrics/sqlmetrics/dbstats_test.go000066400000000000000000000065771410103036700220530ustar00rootroot00000000000000package sqlmetrics import ( "bytes" "database/sql" "fmt" "html/template" "testing" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" ) type dbMock sql.DBStats func (db dbMock) Stats() sql.DBStats { return sql.DBStats(db) } func TestNewDBStatsCollector(t *testing.T) { db := &dbMock{ MaxOpenConnections: 100, OpenConnections: 10, InUse: 3, Idle: 7, WaitCount: 5, WaitDuration: 12, MaxIdleClosed: 7, MaxLifetimeClosed: 8, } dbName := "foo" defaultLabels := prometheus.Labels{dbNameLabel: dbName} tests := []struct { name string opts []DBStatsCollectorOption expectedLabels prometheus.Labels }{ { name: "default", expectedLabels: defaultLabels, }, { name: "with custom labels", opts: []DBStatsCollectorOption{ WithExtraLabels(map[string]string{"x": "y"}), }, expectedLabels: prometheus.Labels{ dbNameLabel: dbName, "x": "y", }, }, { name: "does not override db_name label", opts: []DBStatsCollectorOption{ WithExtraLabels(map[string]string{"db_name": "bar"}), }, expectedLabels: defaultLabels, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { collector := NewDBStatsCollector(dbName, db, tt.opts...) validateMetric(t, collector, maxOpenConnectionsName, maxOpenConnectionsDesc, "gauge", float64(db.MaxOpenConnections), tt.expectedLabels) validateMetric(t, collector, openConnectionsName, openConnectionsDesc, "gauge", float64(db.OpenConnections), tt.expectedLabels) validateMetric(t, collector, inUseName, inUseDesc, "gauge", float64(db.InUse), tt.expectedLabels) validateMetric(t, collector, idleName, idleDesc, "gauge", float64(db.Idle), tt.expectedLabels) validateMetric(t, collector, waitCountName, waitCountDesc, "counter", float64(db.WaitCount), tt.expectedLabels) validateMetric(t, collector, waitDurationName, waitDurationDesc, "counter", db.WaitDuration.Seconds(), tt.expectedLabels) validateMetric(t, collector, maxIdleClosedName, maxIdleClosedDesc, "counter", float64(db.MaxIdleClosed), tt.expectedLabels) validateMetric(t, collector, maxLifetimeClosedName, maxLifetimeClosedDesc, "counter", float64(db.MaxLifetimeClosed), tt.expectedLabels) }) } } type labelsIter struct { Dict prometheus.Labels Counter int } func (l *labelsIter) HasMore() bool { l.Counter++ return l.Counter < len(l.Dict) } func validateMetric(t *testing.T, collector prometheus.Collector, name string, desc string, valueType string, value float64, labels prometheus.Labels) { t.Helper() tmpl := template.New("") tmpl.Delims("[[", "]]") txt := ` # HELP [[.Name]] [[.Desc]] # TYPE [[.Name]] [[.Type]] [[.Name]]{[[range $k, $v := .Labels.Dict]][[$k]]="[[$v]]"[[if $.Labels.HasMore]],[[end]][[end]]} [[.Value]] ` _, err := tmpl.Parse(txt) require.NoError(t, err) var expected bytes.Buffer fullName := fmt.Sprintf("%s_%s_%s", namespace, subsystem, name) err = tmpl.Execute(&expected, struct { Name string Desc string Type string Value float64 Labels *labelsIter }{ Name: fullName, Desc: desc, Labels: &labelsIter{Dict: labels}, Value: value, Type: valueType, }) require.NoError(t, err) err = testutil.CollectAndCompare(collector, &expected, fullName) require.NoError(t, err) } labkit-v1.7.0/metrics/sqlmetrics/examples_test.go000066400000000000000000000007701410103036700222120ustar00rootroot00000000000000package sqlmetrics_test import ( "database/sql" "github.com/prometheus/client_golang/prometheus" "gitlab.com/gitlab-org/labkit/metrics/sqlmetrics" ) func ExampleNewDBStatsCollector() { // Open connection to database db, err := sql.Open("postgres", "postgres://postgres:postgres@localhost:5432/mydb") if err != nil { panic(err) } // Create a new collector collector := sqlmetrics.NewDBStatsCollector("mydb", db) // Register collector with Prometheus prometheus.MustRegister(collector) } labkit-v1.7.0/monitoring/000077500000000000000000000000001410103036700153335ustar00rootroot00000000000000labkit-v1.7.0/monitoring/build_info_gauge.go000066400000000000000000000015311410103036700211440ustar00rootroot00000000000000package monitoring import "github.com/prometheus/client_golang/prometheus" // GitlabBuildInfoGaugeMetricName is the name of the label containing build information for this process. const GitlabBuildInfoGaugeMetricName = "gitlab_build_info" const buildInfoVersionLabel = "version" const buildInfoBuildTimeLabel = "built" // registerBuildInfoGauge registers a label with the current server version // making it easy to see what versions of the application are running across a cluster. func registerBuildInfoGauge(registerer prometheus.Registerer, labels prometheus.Labels) { gitlabBuildInfoGauge := prometheus.NewGauge(prometheus.GaugeOpts{ Name: GitlabBuildInfoGaugeMetricName, Help: "Current build info for this GitLab Service", ConstLabels: labels, }) registerer.MustRegister(gitlabBuildInfoGauge) gitlabBuildInfoGauge.Set(1) } labkit-v1.7.0/monitoring/doc.go000066400000000000000000000037041410103036700164330ustar00rootroot00000000000000/* Package monitoring provides a monitoring endpoint and Continuous Profiling for Go service processes. - The monitoring endpoint will expose OpenMetrics at `/metrics`. - Pprof service endpoints at `/debug/pprof`. More information about the pprof endpoints is available at https://golang.org/pkg/net/http/pprof/ - Support the gitlab_build_info metric for exposing application version information This package includes optional support for continuous profiling, which is responsible for heap and cpu profiling of the Go process. Currently, the only supported driver is Google Cloud Platform's Stackdriver Profiler (see https://cloud.google.com/profiler/). The profiler is initialized upon `monitoring.Start` call. *Compiling applications with Profiler support* For compiling binaries with Stackdriver Profiler support (including its dependencies), build your binary with the `continuous_profiler_stackdriver` compilation tag: * `go build -tags="continuous_profiler_stackdriver"`` *Initializing the Profiler* Use the following pattern in the `GITLAB_CONTINUOUS_PROFILING` environment variable for getting it started: * `GITLAB_CONTINUOUS_PROFILING="stackdriver?service=gitaly&service_version=1.0.1&project_id=test-123"` For more information about each argument see https://godoc.org/cloud.google.com/go/profiler#Config. Most of these shouldn't be required in GCP environments, so a simpler version could be used: * `GITLAB_CONTINUOUS_PROFILING="stackdriver"` See https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application for authentication details. *Profiler overhead* Google Cloud claim Stackdriver Profiler adds a 5% performance overhead to processes when it is enabled. This cost is only incurred when the profiler is switched on (through the `GITLAB_CONTINUOUS_PROFILING`) environment variable. More details can be found at https://medium.com/google-cloud/continuous-profiling-of-go-programs-96d4416af77b. */ package monitoring labkit-v1.7.0/monitoring/examples_test.go000066400000000000000000000010471410103036700205410ustar00rootroot00000000000000package monitoring_test import ( "log" "gitlab.com/gitlab-org/labkit/monitoring" ) func ExampleStart() { go func() { log.Fatal(monitoring.Start( // Listen on port 7822 on all interfaces monitoring.WithListenerAddress(":7822"), // Add the standard version and build time labels monitoring.WithBuildInformation("0.1.1", "2019-09-01T00:22:00Z"), // Add any additional application-specific labels to the `gitlab_build_info` metric monitoring.WithBuildExtraLabels(map[string]string{ "git_version": "2.0.0", }), )) }() } labkit-v1.7.0/monitoring/null_profiler.go000066400000000000000000000002041410103036700205320ustar00rootroot00000000000000// +build !continuous_profiler_stackdriver package monitoring func initProfiler(opts profilerOpts) { // No operation required. } labkit-v1.7.0/monitoring/profiler.go000066400000000000000000000030201410103036700174770ustar00rootroot00000000000000// +build continuous_profiler_stackdriver package monitoring import ( "net/url" "os" "cloud.google.com/go/profiler" "gitlab.com/gitlab-org/labkit/log" "google.golang.org/api/option" ) const profilerEnvKey = "GITLAB_CONTINUOUS_PROFILING" // Exposing to tests. var profStart = profiler.Start func initProfiler(opts profilerOpts) { driverParams, exists := os.LookupEnv(profilerEnvKey) if !exists { return } u, err := url.Parse(driverParams) if err != nil { log.WithError(err).Warn("unable to parse env var content") return } query := u.Query() driverName := u.Path if driverName != "stackdriver" { log.WithField("driver", driverName).Warn("unknown driver") return } config := profiler.Config{ Service: query.Get("service"), ServiceVersion: query.Get("service_version"), ProjectID: query.Get("project_id"), MutexProfiling: true, } // If the service version is given through opts, it takes precedence. if opts.ServiceVersion != "" { config.ServiceVersion = opts.ServiceVersion } var clientOpts []option.ClientOption if opts.CredentialsFile != "" { clientOpts = append(clientOpts, option.WithCredentialsFile(opts.CredentialsFile)) } if err := profStart(config, clientOpts...); err != nil { log.WithError(err).Warnf("unable to initialize %v profiler", driverName) return } log.WithFields(log.Fields{ "driver": driverName, "service": config.Service, "serviceVersion": config.ServiceVersion, "projectId": config.ProjectID, }).Info("profiler enabled") } labkit-v1.7.0/monitoring/profiler_opts.go000066400000000000000000000002541410103036700205520ustar00rootroot00000000000000package monitoring // A generic profiler config holder. Avoid // adding driver-specific data. type profilerOpts struct { ServiceVersion string CredentialsFile string } labkit-v1.7.0/monitoring/profiler_test.go000066400000000000000000000117251410103036700205510ustar00rootroot00000000000000// +build continuous_profiler_stackdriver package monitoring import ( "bytes" "fmt" "os" "testing" "cloud.google.com/go/profiler" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/labkit/log" "google.golang.org/api/option" ) func TestInitProfiler(t *testing.T) { tests := []struct { name string envParams string writeEnvVar bool opts profilerOpts expectedLog string profErr error expectedConfig profiler.Config expectedOptions []option.ClientOption }{ { name: "complete params", opts: profilerOpts{}, envParams: "stackdriver?service=gitaly&service_version=1.0.1&project_id=test-123", writeEnvVar: true, expectedLog: `^time=.*level=info msg="profiler enabled" driver=stackdriver projectId=test-123 service=gitaly serviceVersion=1.0.1`, expectedConfig: profiler.Config{Service: "gitaly", ServiceVersion: "1.0.1", ProjectID: "test-123", MutexProfiling: true}, }, { name: "complete env params and service version given", opts: profilerOpts{ServiceVersion: "9.0.0"}, envParams: "stackdriver?service=gitaly&service_version=1.0.1&project_id=test-123", writeEnvVar: true, expectedLog: `^time=.*level=info msg="profiler enabled" driver=stackdriver projectId=test-123 service=gitaly serviceVersion=9.0.0`, expectedConfig: profiler.Config{Service: "gitaly", ServiceVersion: "9.0.0", ProjectID: "test-123", MutexProfiling: true}, }, { name: "incomplete env params and service version given through args", opts: profilerOpts{ServiceVersion: "9.0.0"}, envParams: "stackdriver?service=gitaly", expectedLog: `^time=.*level=info msg="profiler enabled" driver=stackdriver projectId= service=gitaly serviceVersion=9.0.0`, expectedConfig: profiler.Config{MutexProfiling: true}, }, { name: "unknown driver", envParams: "true?service=gitaly", writeEnvVar: true, opts: profilerOpts{}, expectedLog: `^time=.*level=warning msg="unknown driver" driver=true`, expectedConfig: profiler.Config{MutexProfiling: true}, }, { name: "wrong env content format", envParams: ":foo?service=gitaly", writeEnvVar: true, opts: profilerOpts{}, expectedLog: `^time=.*level=warning msg="unable to parse env var content"`, expectedConfig: profiler.Config{MutexProfiling: true}, }, { name: "empty logs when no env var is set", writeEnvVar: false, opts: profilerOpts{}, }, { name: "fails to start profiler", envParams: "stackdriver?service=gitaly&service_version=1.0.1&project_id=test-123", writeEnvVar: true, opts: profilerOpts{}, expectedLog: `^time=.*level=warning msg="unable to initialize stackdriver profiler" error="fail to start"`, profErr: fmt.Errorf("fail to start"), expectedConfig: profiler.Config{Service: "gitaly", ServiceVersion: "1.0.1", ProjectID: "test-123", MutexProfiling: true}, }, { name: "with credentials file", envParams: "stackdriver", writeEnvVar: true, opts: profilerOpts{CredentialsFile: "/path/to/credentials.json"}, expectedOptions: []option.ClientOption{option.WithCredentialsFile("/path/to/credentials.json")}, expectedConfig: profiler.Config{MutexProfiling: true}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { clearGcpEnvVars(t) preStubStart := profStart defer func() { profStart = preStubStart }() stubStackdriverProfiler(t, test.profErr, test.expectedConfig, test.expectedOptions) if test.writeEnvVar { err := os.Setenv(profilerEnvKey, test.envParams) require.NoError(t, err) } logOutput := captureLogOutput(t, initProfiler, test.opts) if test.writeEnvVar { require.Regexp(t, test.expectedLog, logOutput) } else { require.Empty(t, logOutput) } }) } } func TestInitProfilerWithNoProfilerEnvKeySet(t *testing.T) { t.Run("no GITLAB_CONTINUOUS_PROFILING env var set", func(t *testing.T) { clearGcpEnvVars(t) logOutput := captureLogOutput(t, initProfiler, profilerOpts{}) require.Empty(t, logOutput) }) } func stubStackdriverProfiler(t *testing.T, err error, expectedConfig profiler.Config, expectedOptions []option.ClientOption) { t.Helper() profStart = func(cfg profiler.Config, options ...option.ClientOption) error { require.Equal(t, expectedConfig, cfg) require.Equal(t, expectedOptions, options) return err } } func captureLogOutput(t *testing.T, init func(opts profilerOpts), opts profilerOpts) string { t.Helper() buf := &bytes.Buffer{} closer, err := log.Initialize(log.WithWriter(buf)) require.NoError(t, err) defer closer.Close() init(opts) return buf.String() } func clearGcpEnvVars(t *testing.T) { t.Helper() for _, env := range []string{profilerEnvKey, "GAE_SERVICE", "GAE_VERSION", "GOOGLE_CLOUD_PROJECT"} { err := os.Unsetenv(env) require.NoError(t, err) } } labkit-v1.7.0/monitoring/start.go000066400000000000000000000047621410103036700170300ustar00rootroot00000000000000package monitoring import ( "log" "net/http" "net/http/pprof" "github.com/prometheus/client_golang/prometheus/promhttp" ) // Start will start a new monitoring service listening on the address // configured through the option arguments. Additionally, it'll start // a Continuous Profiler configured through environment variables // (see more at https://gitlab.com/gitlab-org/labkit/-/blob/master/monitoring/doc.go). // // If `WithListenerAddress` option is provided, Start will block or return a non-nil error, // similar to `http.ListenAndServe` (for instance). func Start(options ...Option) error { config := applyOptions(options) listener, err := config.listenerFactory() if err != nil { return err } // Initialize the Continuous Profiler. if !config.continuousProfilingDisabled { profOpts := profilerOpts{ ServiceVersion: config.version, CredentialsFile: config.profilerCredentialsFile, } initProfiler(profOpts) } if listener == nil { // No listener has been configured, skip mux setup. return nil } metricsHandler(config) pprofHandlers(config) return http.Serve(listener, config.serveMux) } func metricsHandler(cfg optionsConfig) { if cfg.metricsDisabled { return } // Register the `gitlab_build_info` metric if configured if len(cfg.buildInfoGaugeLabels) > 0 { registerBuildInfoGauge(cfg.registerer, cfg.buildInfoGaugeLabels) } cfg.serveMux.Handle( cfg.metricsHandlerPattern, promhttp.InstrumentMetricHandler(cfg.registerer, promhttp.HandlerFor(cfg.gatherer, promhttp.HandlerOpts{})), ) } func pprofHandlers(cfg optionsConfig) { if cfg.pprofDisabled { return } cfg.serveMux.HandleFunc("/debug/pprof/", pprof.Index) cfg.serveMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) cfg.serveMux.HandleFunc("/debug/pprof/profile", pprof.Profile) cfg.serveMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) cfg.serveMux.HandleFunc("/debug/pprof/trace", pprof.Trace) } // Serve will start a new monitoring service listening on the address // configured through the option arguments. Additionally, it'll start // a Continuous Profiler configured through environment variables // (see more at https://gitlab.com/gitlab-org/labkit/-/blob/master/monitoring/doc.go). // // If `WithListenerAddress` option is provided, Serve will block or return a non-nil error, // similar to `http.ListenAndServe` (for instance). // // Deprecated: Use Start instead. func Serve(options ...Option) error { log.Print("warning: deprecated function, use Start instead") return Start(options...) } labkit-v1.7.0/monitoring/start_options.go000066400000000000000000000100661410103036700205750ustar00rootroot00000000000000package monitoring import ( "net" "net/http" "github.com/prometheus/client_golang/prometheus" ) type listenerFactory func() (net.Listener, error) type optionsConfig struct { listenerFactory listenerFactory buildInfoGaugeLabels prometheus.Labels registerer prometheus.Registerer gatherer prometheus.Gatherer version string continuousProfilingDisabled bool metricsDisabled bool pprofDisabled bool metricsHandlerPattern string profilerCredentialsFile string serveMux *http.ServeMux } // Option is used to pass options to NewListener. type Option func(*optionsConfig) func defaultOptions() optionsConfig { return optionsConfig{ listenerFactory: defaultListenerFactory, buildInfoGaugeLabels: prometheus.Labels{}, registerer: prometheus.DefaultRegisterer, gatherer: prometheus.DefaultGatherer, metricsHandlerPattern: "/metrics", serveMux: http.NewServeMux(), } } func applyOptions(opts []Option) optionsConfig { config := defaultOptions() for _, v := range opts { v(&config) } return config } // WithListener will configure the health check endpoint to use the provided listener. func WithListener(listener net.Listener) Option { return func(config *optionsConfig) { config.listenerFactory = func() (net.Listener, error) { return listener, nil } } } // WithListenerAddress will configure the health check endpoint to use the provided listener. func WithListenerAddress(addr string) Option { return func(config *optionsConfig) { config.listenerFactory = func() (net.Listener, error) { return net.Listen("tcp", addr) } } } // WithBuildInformation will configure the `gitlab_build_info` metric with appropriate labels. func WithBuildInformation(version string, buildTime string) Option { return func(config *optionsConfig) { config.buildInfoGaugeLabels[buildInfoVersionLabel] = version config.buildInfoGaugeLabels[buildInfoBuildTimeLabel] = buildTime config.version = version } } // WithBuildExtraLabels will configure extra labels on the `gitlab_build_info` metric. func WithBuildExtraLabels(labels map[string]string) Option { return func(config *optionsConfig) { for key, v := range labels { config.buildInfoGaugeLabels[key] = v } } } // WithMetricsHandlerPattern configures the pattern that the metrics handler is registered for (defaults to `/metrics`). func WithMetricsHandlerPattern(pattern string) Option { return func(config *optionsConfig) { config.metricsHandlerPattern = pattern } } // WithoutContinuousProfiling disables the continuous profiler. func WithoutContinuousProfiling() Option { return func(config *optionsConfig) { config.continuousProfilingDisabled = true } } // WithoutMetrics disables the metrics endpoint. func WithoutMetrics() Option { return func(config *optionsConfig) { config.metricsDisabled = true } } // WithoutPprof disables the pprof endpoint. func WithoutPprof() Option { return func(config *optionsConfig) { config.pprofDisabled = true } } // WithProfilerCredentialsFile configures the credentials file to be used for the profiler service. func WithProfilerCredentialsFile(path string) Option { return func(config *optionsConfig) { config.profilerCredentialsFile = path } } // WithPrometheusGatherer sets the prometheus.Gatherer to expose in the metrics endpoint. func WithPrometheusGatherer(gatherer prometheus.Gatherer) Option { return func(config *optionsConfig) { config.gatherer = gatherer } } // WithPrometheusRegisterer sets the prometheus.Registerer to use to register metrics from this package. func WithPrometheusRegisterer(registerer prometheus.Registerer) Option { return func(config *optionsConfig) { config.registerer = registerer } } // WithServeMux will configure the health check endpoint to use the provided http.ServeMux. func WithServeMux(mux *http.ServeMux) Option { return func(config *optionsConfig) { config.serveMux = mux } } func defaultListenerFactory() (net.Listener, error) { return nil, nil } labkit-v1.7.0/monitoring/start_options_test.go000066400000000000000000000237731410103036700216450ustar00rootroot00000000000000package monitoring import ( "net" "net/http" "testing" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type customGatherer struct { } func (c customGatherer) Gather() ([]*dto.MetricFamily, error) { return nil, nil } type customRegisterer struct { } func (c customRegisterer) Register(collector prometheus.Collector) error { return nil } func (c customRegisterer) MustRegister(collector ...prometheus.Collector) { } func (c customRegisterer) Unregister(collector prometheus.Collector) bool { return false } func checkDefaultGatherer(t *testing.T, g prometheus.Gatherer) { t.Helper() assert.Equal(t, prometheus.DefaultGatherer, g) } func checkCustomGatherer(t *testing.T, g prometheus.Gatherer) { t.Helper() assert.Equal(t, customGatherer{}, g) } func checkDefaultRegisterer(t *testing.T, g prometheus.Registerer) { t.Helper() assert.Equal(t, prometheus.DefaultRegisterer, g) } func checkCustomRegisterer(t *testing.T, g prometheus.Registerer) { t.Helper() assert.Equal(t, customRegisterer{}, g) } func checkDefaultListener(t *testing.T, f listenerFactory) { t.Helper() gotListener, err := f() require.Exactly(t, nil, gotListener) require.NoError(t, err) } func isTCPListener(t *testing.T, f listenerFactory) { t.Helper() gotListener, err := f() require.NoError(t, err) require.IsType(t, &net.TCPListener{}, gotListener) } func Test_applyOptions(t *testing.T) { testListener, err := net.Listen("tcp", ":0") require.NoError(t, err) defer testListener.Close() isTestListener := func(t *testing.T, f listenerFactory) { t.Helper() gotListener, err := f() require.NoError(t, err) require.Exactly(t, testListener, gotListener) } testMux := http.NewServeMux() testMux.HandleFunc("/debug/foo", func(writer http.ResponseWriter, r *http.Request) {}) tests := []struct { name string opts []Option wantListenerCheck func(t *testing.T, f listenerFactory) wantGathererCheck func(t *testing.T, g prometheus.Gatherer) wantRegistererCheck func(t *testing.T, g prometheus.Registerer) wantbuildInfoGaugeLabels prometheus.Labels wantVersion string wantContinuousProfilingDisabled bool wantMetricsDisabled bool wantPprofDisabled bool wantMetricsHandlerPattern string wantProfilerCredentialsFile string wantServeMux *http.ServeMux }{ { name: "empty", opts: nil, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "with_listener", opts: []Option{WithListener(testListener)}, wantListenerCheck: isTestListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "with_listen_address", opts: []Option{WithListenerAddress(":0")}, wantListenerCheck: isTCPListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "with_build_information", opts: []Option{WithBuildInformation("1.0.0", "2018-01-02T00:00:00Z")}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantbuildInfoGaugeLabels: prometheus.Labels{"built": "2018-01-02T00:00:00Z", "version": "1.0.0"}, wantVersion: "1.0.0", wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "with_build_extra_labels", opts: []Option{WithBuildExtraLabels(map[string]string{"git_version": "2.0.0"})}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantbuildInfoGaugeLabels: prometheus.Labels{"git_version": "2.0.0"}, wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "with_build_information_and_extra_labels", opts: []Option{ WithBuildInformation("1.0.0", "2018-01-02T00:00:00Z"), WithBuildExtraLabels(map[string]string{"git_version": "2.0.0"}), }, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantbuildInfoGaugeLabels: prometheus.Labels{ "built": "2018-01-02T00:00:00Z", "version": "1.0.0", "git_version": "2.0.0", }, wantVersion: "1.0.0", wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "combined", opts: []Option{ WithListenerAddress(":0"), WithBuildInformation("1.0.0", "2018-01-02T00:00:00Z"), WithBuildExtraLabels(map[string]string{"git_version": "2.0.0"}), }, wantListenerCheck: isTCPListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantbuildInfoGaugeLabels: prometheus.Labels{ "built": "2018-01-02T00:00:00Z", "version": "1.0.0", "git_version": "2.0.0", }, wantVersion: "1.0.0", wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "without continuous profiling", opts: []Option{WithoutContinuousProfiling()}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantContinuousProfilingDisabled: true, wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "without metrics", opts: []Option{WithoutMetrics()}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantMetricsDisabled: true, wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "without pprof", opts: []Option{WithoutPprof()}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantPprofDisabled: true, wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "with custom metrics handler pattern", opts: []Option{WithMetricsHandlerPattern("/foo")}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantMetricsHandlerPattern: "/foo", wantServeMux: http.NewServeMux(), }, { name: "with profiler credentials file", opts: []Option{WithProfilerCredentialsFile("/path/to/credentials.json")}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantMetricsHandlerPattern: "/metrics", wantProfilerCredentialsFile: "/path/to/credentials.json", wantServeMux: http.NewServeMux(), }, { name: "with custom gatherer", opts: []Option{WithPrometheusGatherer(customGatherer{})}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkCustomGatherer, wantRegistererCheck: checkDefaultRegisterer, wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "with custom registerer", opts: []Option{WithPrometheusRegisterer(customRegisterer{})}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkCustomRegisterer, wantMetricsHandlerPattern: "/metrics", wantServeMux: http.NewServeMux(), }, { name: "with custom mux", opts: []Option{WithServeMux(testMux)}, wantListenerCheck: checkDefaultListener, wantGathererCheck: checkDefaultGatherer, wantRegistererCheck: checkDefaultRegisterer, wantMetricsHandlerPattern: "/metrics", wantServeMux: testMux, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := applyOptions(tt.opts) tt.wantListenerCheck(t, got.listenerFactory) tt.wantGathererCheck(t, got.gatherer) tt.wantRegistererCheck(t, got.registerer) require.Equal(t, tt.wantVersion, got.version) require.Equal(t, tt.wantContinuousProfilingDisabled, got.continuousProfilingDisabled) require.Equal(t, tt.wantMetricsDisabled, got.metricsDisabled) require.Equal(t, tt.wantPprofDisabled, got.pprofDisabled) require.Equal(t, tt.wantMetricsHandlerPattern, got.metricsHandlerPattern) require.Equal(t, tt.wantProfilerCredentialsFile, got.profilerCredentialsFile) require.Equal(t, tt.wantServeMux, got.serveMux) if tt.wantbuildInfoGaugeLabels == nil { require.Equal(t, prometheus.Labels{}, got.buildInfoGaugeLabels) } else { require.Equal(t, tt.wantbuildInfoGaugeLabels, got.buildInfoGaugeLabels) } }) } } labkit-v1.7.0/package-lock.json000066400000000000000000006034531410103036700163750ustar00rootroot00000000000000{ "name": "labkit", "requires": true, "lockfileVersion": 1, "dependencies": { "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "dev": true, "requires": { "@babel/highlight": "^7.10.4" } }, "@babel/helper-validator-identifier": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", "dev": true }, "@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/runtime": { "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } }, "@commitlint/cli": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-11.0.0.tgz", "integrity": "sha512-YWZWg1DuqqO5Zjh7vUOeSX76vm0FFyz4y0cpGMFhrhvUi5unc4IVfCXZ6337R9zxuBtmveiRuuhQqnRRer+13g==", "dev": true, "requires": { "@babel/runtime": "^7.11.2", "@commitlint/format": "^11.0.0", "@commitlint/lint": "^11.0.0", "@commitlint/load": "^11.0.0", "@commitlint/read": "^11.0.0", "chalk": "4.1.0", "core-js": "^3.6.1", "get-stdin": "8.0.0", "lodash": "^4.17.19", "resolve-from": "5.0.0", "resolve-global": "1.0.0", "yargs": "^15.1.0" }, "dependencies": { "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" } }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, "@commitlint/config-conventional": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-11.0.0.tgz", "integrity": "sha512-SNDRsb5gLuDd2PL83yCOQX6pE7gevC79UPFx+GLbLfw6jGnnbO9/tlL76MLD8MOViqGbo7ZicjChO9Gn+7tHhA==", "dev": true, "requires": { "conventional-changelog-conventionalcommits": "^4.3.1" } }, "@commitlint/ensure": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-11.0.0.tgz", "integrity": "sha512-/T4tjseSwlirKZdnx4AuICMNNlFvRyPQimbZIOYujp9DSO6XRtOy9NrmvWujwHsq9F5Wb80QWi4WMW6HMaENug==", "dev": true, "requires": { "@commitlint/types": "^11.0.0", "lodash": "^4.17.19" } }, "@commitlint/execute-rule": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-11.0.0.tgz", "integrity": "sha512-g01p1g4BmYlZ2+tdotCavrMunnPFPhTzG1ZiLKTCYrooHRbmvqo42ZZn4QMStUEIcn+jfLb6BRZX3JzIwA1ezQ==", "dev": true }, "@commitlint/format": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-11.0.0.tgz", "integrity": "sha512-bpBLWmG0wfZH/svzqD1hsGTpm79TKJWcf6EXZllh2J/LSSYKxGlv967lpw0hNojme0sZd4a/97R3qA2QHWWSLg==", "dev": true, "requires": { "@commitlint/types": "^11.0.0", "chalk": "^4.0.0" }, "dependencies": { "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" } }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, "@commitlint/is-ignored": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-11.0.0.tgz", "integrity": "sha512-VLHOUBN+sOlkYC4tGuzE41yNPO2w09sQnOpfS+pSPnBFkNUUHawEuA44PLHtDvQgVuYrMAmSWFQpWabMoP5/Xg==", "dev": true, "requires": { "@commitlint/types": "^11.0.0", "semver": "7.3.2" } }, "@commitlint/lint": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-11.0.0.tgz", "integrity": "sha512-Q8IIqGIHfwKr8ecVZyYh6NtXFmKw4YSEWEr2GJTB/fTZXgaOGtGFZDWOesCZllQ63f1s/oWJYtVv5RAEuwN8BQ==", "dev": true, "requires": { "@commitlint/is-ignored": "^11.0.0", "@commitlint/parse": "^11.0.0", "@commitlint/rules": "^11.0.0", "@commitlint/types": "^11.0.0" } }, "@commitlint/load": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-11.0.0.tgz", "integrity": "sha512-t5ZBrtgvgCwPfxmG811FCp39/o3SJ7L+SNsxFL92OR4WQxPcu6c8taD0CG2lzOHGuRyuMxZ7ps3EbngT2WpiCg==", "dev": true, "requires": { "@commitlint/execute-rule": "^11.0.0", "@commitlint/resolve-extends": "^11.0.0", "@commitlint/types": "^11.0.0", "chalk": "4.1.0", "cosmiconfig": "^7.0.0", "lodash": "^4.17.19", "resolve-from": "^5.0.0" }, "dependencies": { "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" } }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "cosmiconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, "@commitlint/message": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-11.0.0.tgz", "integrity": "sha512-01ObK/18JL7PEIE3dBRtoMmU6S3ecPYDTQWWhcO+ErA3Ai0KDYqV5VWWEijdcVafNpdeUNrEMigRkxXHQLbyJA==", "dev": true }, "@commitlint/parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-11.0.0.tgz", "integrity": "sha512-DekKQAIYWAXIcyAZ6/PDBJylWJ1BROTfDIzr9PMVxZRxBPc1gW2TG8fLgjZfBP5mc0cuthPkVi91KQQKGri/7A==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", "conventional-commits-parser": "^3.0.0" } }, "@commitlint/read": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-11.0.0.tgz", "integrity": "sha512-37V0V91GSv0aDzMzJioKpCoZw6l0shk7+tRG8RkW1GfZzUIytdg3XqJmM+IaIYpaop0m6BbZtfq+idzUwJnw7g==", "dev": true, "requires": { "@commitlint/top-level": "^11.0.0", "fs-extra": "^9.0.0", "git-raw-commits": "^2.0.0" } }, "@commitlint/resolve-extends": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-11.0.0.tgz", "integrity": "sha512-WinU6Uv6L7HDGLqn/To13KM1CWvZ09VHZqryqxXa1OY+EvJkfU734CwnOEeNlSCK7FVLrB4kmodLJtL1dkEpXw==", "dev": true, "requires": { "import-fresh": "^3.0.0", "lodash": "^4.17.19", "resolve-from": "^5.0.0", "resolve-global": "^1.0.0" } }, "@commitlint/rules": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-11.0.0.tgz", "integrity": "sha512-2hD9y9Ep5ZfoNxDDPkQadd2jJeocrwC4vJ98I0g8pNYn/W8hS9+/FuNpolREHN8PhmexXbkjrwyQrWbuC0DVaA==", "dev": true, "requires": { "@commitlint/ensure": "^11.0.0", "@commitlint/message": "^11.0.0", "@commitlint/to-lines": "^11.0.0", "@commitlint/types": "^11.0.0" } }, "@commitlint/to-lines": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-11.0.0.tgz", "integrity": "sha512-TIDTB0Y23jlCNubDROUVokbJk6860idYB5cZkLWcRS9tlb6YSoeLn1NLafPlrhhkkkZzTYnlKYzCVrBNVes1iw==", "dev": true }, "@commitlint/top-level": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-11.0.0.tgz", "integrity": "sha512-O0nFU8o+Ws+py5pfMQIuyxOtfR/kwtr5ybqTvR+C2lUPer2x6lnQU+OnfD7hPM+A+COIUZWx10mYQvkR3MmtAA==", "dev": true, "requires": { "find-up": "^5.0.0" }, "dependencies": { "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { "p-locate": "^5.0.0" } }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { "yocto-queue": "^0.1.0" } }, "p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { "p-limit": "^3.0.2" } } } }, "@commitlint/types": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-11.0.0.tgz", "integrity": "sha512-VoNqai1vR5anRF5Tuh/+SWDFk7xi7oMwHrHrbm1BprYXjB2RJsWLhUrStMssDxEl5lW/z3EUdg8RvH/IUBccSQ==", "dev": true }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true }, "@nodelib/fs.walk": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "@octokit/auth-token": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz", "integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==", "dev": true, "requires": { "@octokit/types": "^6.0.3" } }, "@octokit/core": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz", "integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==", "dev": true, "requires": { "@octokit/auth-token": "^2.4.4", "@octokit/graphql": "^4.5.8", "@octokit/request": "^5.6.0", "@octokit/request-error": "^2.0.5", "@octokit/types": "^6.0.3", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "@octokit/endpoint": { "version": "6.0.12", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", "dev": true, "requires": { "@octokit/types": "^6.0.3", "is-plain-object": "^5.0.0", "universal-user-agent": "^6.0.0" } }, "@octokit/graphql": { "version": "4.6.4", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.4.tgz", "integrity": "sha512-SWTdXsVheRmlotWNjKzPOb6Js6tjSqA2a8z9+glDJng0Aqjzti8MEWOtuT8ZSu6wHnci7LZNuarE87+WJBG4vg==", "dev": true, "requires": { "@octokit/request": "^5.6.0", "@octokit/types": "^6.0.3", "universal-user-agent": "^6.0.0" } }, "@octokit/openapi-types": { "version": "8.1.4", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-8.1.4.tgz", "integrity": "sha512-NnGr4NNDqO5wjSDJo5nxrGtzZUwoT23YasqK2H4Pav/6vSgeVTxuqCL9Aeh+cWfTxDomj1M4Os5BrXFsvl7qiQ==", "dev": true }, "@octokit/plugin-paginate-rest": { "version": "2.13.6", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.6.tgz", "integrity": "sha512-ai7TNKLi8tGkDvLM7fm0X1fbIP9u1nfXnN49ZAw2PgSoQou9yixKn5c3m0awuLacbuX2aXEvJpv1gKm3jboabg==", "dev": true, "requires": { "@octokit/types": "^6.17.3" } }, "@octokit/plugin-request-log": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", "dev": true }, "@octokit/plugin-rest-endpoint-methods": { "version": "5.3.7", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.3.7.tgz", "integrity": "sha512-LAgTLOsJ86ig2wYSpcSx+UWt7aQYYsEZ/Tf/pksAVQWKNcGuTVCDl9OUiPhQ7DZelNozYVWTO9Iyjd/soe4tug==", "dev": true, "requires": { "@octokit/types": "^6.17.4", "deprecation": "^2.3.1" } }, "@octokit/request": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.0.tgz", "integrity": "sha512-4cPp/N+NqmaGQwbh3vUsYqokQIzt7VjsgTYVXiwpUP2pxd5YiZB2XuTedbb0SPtv9XS7nzAKjAuQxmY8/aZkiA==", "dev": true, "requires": { "@octokit/endpoint": "^6.0.1", "@octokit/request-error": "^2.1.0", "@octokit/types": "^6.16.1", "is-plain-object": "^5.0.0", "node-fetch": "^2.6.1", "universal-user-agent": "^6.0.0" } }, "@octokit/request-error": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", "dev": true, "requires": { "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "@octokit/rest": { "version": "18.6.6", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.6.6.tgz", "integrity": "sha512-kCLvz8MSh+KToXySdqUp80caBom1ZQmsX3gbT3osfbJy6fD86QObUjzAOD3D3Awz3X7ng24+lB+imvSr5EnM7g==", "dev": true, "requires": { "@octokit/core": "^3.5.0", "@octokit/plugin-paginate-rest": "^2.6.2", "@octokit/plugin-request-log": "^1.0.2", "@octokit/plugin-rest-endpoint-methods": "5.3.7" } }, "@octokit/types": { "version": "6.17.4", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.17.4.tgz", "integrity": "sha512-Ghk/JC4zC/1al1GwH6p8jVX6pLdypSWmbnx6h79C/yo3DeaDd6MsNsBFlHu22KbkFh+CdcAzFqdP7UdPaPPmmA==", "dev": true, "requires": { "@octokit/openapi-types": "^8.1.4" } }, "@semantic-release/commit-analyzer": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-8.0.1.tgz", "integrity": "sha512-5bJma/oB7B4MtwUkZC2Bf7O1MHfi4gWe4mA+MIQ3lsEV0b422Bvl1z5HRpplDnMLHH3EXMoRdEng6Ds5wUqA3A==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", "conventional-commits-filter": "^2.0.0", "conventional-commits-parser": "^3.0.7", "debug": "^4.0.0", "import-from": "^3.0.0", "lodash": "^4.17.4", "micromatch": "^4.0.2" } }, "@semantic-release/error": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==", "dev": true }, "@semantic-release/github": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-7.2.3.tgz", "integrity": "sha512-lWjIVDLal+EQBzy697ayUNN8MoBpp+jYIyW2luOdqn5XBH4d9bQGfTnjuLyzARZBHejqh932HVjiH/j4+R7VHw==", "dev": true, "requires": { "@octokit/rest": "^18.0.0", "@semantic-release/error": "^2.2.0", "aggregate-error": "^3.0.0", "bottleneck": "^2.18.1", "debug": "^4.0.0", "dir-glob": "^3.0.0", "fs-extra": "^10.0.0", "globby": "^11.0.0", "http-proxy-agent": "^4.0.0", "https-proxy-agent": "^5.0.0", "issue-parser": "^6.0.0", "lodash": "^4.17.4", "mime": "^2.4.3", "p-filter": "^2.0.0", "p-retry": "^4.0.0", "url-join": "^4.0.0" }, "dependencies": { "fs-extra": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true } } }, "@semantic-release/gitlab": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/@semantic-release/gitlab/-/gitlab-6.2.1.tgz", "integrity": "sha512-hbUzMRf9/ZsHRoWCNYs1NM9Au8X2DQzBexnWavU24GVWZw3r15aEZl+lY0fTbcRdQtgXZxOH3TDiW5fuTbvb9A==", "dev": true, "requires": { "@semantic-release/error": "^2.2.0", "aggregate-error": "^3.0.0", "debug": "^4.0.0", "dir-glob": "^3.0.0", "escape-string-regexp": "^3.0.0", "form-data": "^3.0.0", "fs-extra": "^9.0.0", "globby": "^11.0.0", "got": "^10.5.2", "lodash": "^4.17.11", "parse-path": "^4.0.0", "url-join": "^4.0.0" } }, "@semantic-release/npm": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-7.1.3.tgz", "integrity": "sha512-x52kQ/jR09WjuWdaTEHgQCvZYMOTx68WnS+TZ4fya5ZAJw4oRtJETtrvUw10FdfM28d/keInQdc66R1Gw5+OEQ==", "dev": true, "requires": { "@semantic-release/error": "^2.2.0", "aggregate-error": "^3.0.0", "execa": "^5.0.0", "fs-extra": "^10.0.0", "lodash": "^4.17.15", "nerf-dart": "^1.0.0", "normalize-url": "^6.0.0", "npm": "^7.0.0", "rc": "^1.2.8", "read-pkg": "^5.0.0", "registry-auth-token": "^4.0.0", "semver": "^7.1.2", "tempy": "^1.0.0" }, "dependencies": { "fs-extra": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true } } }, "@semantic-release/release-notes-generator": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.3.tgz", "integrity": "sha512-hMZyddr0u99OvM2SxVOIelHzly+PP3sYtJ8XOLHdMp8mrluN5/lpeTnIO27oeCYdupY/ndoGfvrqDjHqkSyhVg==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", "conventional-changelog-writer": "^4.0.0", "conventional-commits-filter": "^2.0.0", "conventional-commits-parser": "^3.0.0", "debug": "^4.0.0", "get-stream": "^6.0.0", "import-from": "^3.0.0", "into-stream": "^6.0.0", "lodash": "^4.17.4", "read-pkg-up": "^7.0.0" }, "dependencies": { "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true } } }, "@sindresorhus/is": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", "dev": true }, "@szmarczak/http-timer": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", "dev": true, "requires": { "defer-to-connect": "^2.0.0" } }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, "@types/cacheable-request": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", "dev": true, "requires": { "@types/http-cache-semantics": "*", "@types/keyv": "*", "@types/node": "*", "@types/responselike": "*" } }, "@types/http-cache-semantics": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", "dev": true }, "@types/keyv": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", "dev": true, "requires": { "@types/node": "*" } }, "@types/minimist": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", "dev": true }, "@types/node": { "version": "15.12.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.5.tgz", "integrity": "sha512-se3yX7UHv5Bscf8f1ERKvQOD6sTyycH3hdaoozvaLxgUiY5lIGEeH37AD0G0Qi9kPqihPn0HOfd2yaIEN9VwEg==", "dev": true }, "@types/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", "dev": true, "requires": { "@types/node": "*" } }, "@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, "JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "dev": true, "requires": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" } }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "requires": { "debug": "4" } }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "requires": { "type-fest": "^0.21.3" }, "dependencies": { "type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true } } }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" } }, "ansicolors": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, "argv-formatter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", "integrity": "sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk=", "dev": true }, "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, "at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, "before-after-hook": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", "dev": true }, "bottleneck": { "version": "2.19.5", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { "fill-range": "^7.0.1" } }, "cacheable-lookup": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz", "integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==", "dev": true, "requires": { "@types/keyv": "^3.1.1", "keyv": "^4.0.0" } }, "cacheable-request": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", "dev": true, "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" } }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "camelcase-keys": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "dev": true, "requires": { "camelcase": "^5.3.1", "map-obj": "^4.0.0", "quick-lru": "^4.0.1" } }, "cardinal": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", "dev": true, "requires": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" } }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" }, "dependencies": { "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true } } }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, "cli-table": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz", "integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==", "dev": true, "requires": { "colors": "1.0.3" } }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", "dev": true, "requires": { "mimic-response": "^1.0.0" }, "dependencies": { "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true } } }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { "color-name": "1.1.3" } }, "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "colors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, "compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "requires": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "conventional-changelog-angular": { "version": "5.0.12", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.12.tgz", "integrity": "sha512-5GLsbnkR/7A89RyHLvvoExbiGbd9xKdKqDTrArnPbOqBqG/2wIosu0fHwpeIRI8Tl94MhVNBXcLJZl92ZQ5USw==", "dev": true, "requires": { "compare-func": "^2.0.0", "q": "^1.5.1" } }, "conventional-changelog-conventionalcommits": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.0.tgz", "integrity": "sha512-sj9tj3z5cnHaSJCYObA9nISf7eq/YjscLPoq6nmew4SiOjxqL2KRpK20fjnjVbpNDjJ2HR3MoVcWKXwbVvzS0A==", "dev": true, "requires": { "compare-func": "^2.0.0", "lodash": "^4.17.15", "q": "^1.5.1" } }, "conventional-changelog-writer": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz", "integrity": "sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw==", "dev": true, "requires": { "compare-func": "^2.0.0", "conventional-commits-filter": "^2.0.7", "dateformat": "^3.0.0", "handlebars": "^4.7.6", "json-stringify-safe": "^5.0.1", "lodash": "^4.17.15", "meow": "^8.0.0", "semver": "^6.0.0", "split": "^1.0.0", "through2": "^4.0.0" }, "dependencies": { "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "conventional-commits-filter": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", "dev": true, "requires": { "lodash.ismatch": "^4.4.0", "modify-values": "^1.0.0" } }, "conventional-commits-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.0.tgz", "integrity": "sha512-XmJiXPxsF0JhAKyfA2Nn+rZwYKJ60nanlbSWwwkGwLQFbugsc0gv1rzc7VbbUWAzJfR1qR87/pNgv9NgmxtBMQ==", "dev": true, "requires": { "JSONStream": "^1.0.4", "is-text-path": "^1.0.1", "lodash": "^4.17.15", "meow": "^8.0.0", "split2": "^2.0.0", "through2": "^4.0.0", "trim-off-newlines": "^1.0.0" } }, "core-js": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.0.tgz", "integrity": "sha512-W2VYNB0nwQQE7tKS7HzXd7r2y/y2SVJl4ga6oH/dnaLFzM0o2lB2P3zCkWj5Wc/zyMYjtgd5Hmhk0ObkQFZOIA==", "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, "cosmiconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, "dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", "dev": true }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", "dev": true }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { "ms": "2.1.2" } }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "decamelize-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", "dev": true, "requires": { "decamelize": "^1.1.0", "map-obj": "^1.0.0" }, "dependencies": { "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true } } }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, "decompress-response": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", "dev": true, "requires": { "mimic-response": "^2.0.0" } }, "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, "defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true }, "del": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", "dev": true, "requires": { "globby": "^11.0.1", "graceful-fs": "^4.2.4", "is-glob": "^4.0.1", "is-path-cwd": "^2.2.0", "is-path-inside": "^3.0.2", "p-map": "^4.0.0", "rimraf": "^3.0.2", "slash": "^3.0.0" }, "dependencies": { "p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "requires": { "aggregate-error": "^3.0.0" } } } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, "deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", "dev": true }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { "path-type": "^4.0.0" } }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "requires": { "is-obj": "^2.0.0" } }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, "requires": { "readable-stream": "^2.0.2" } }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { "once": "^1.4.0" } }, "env-ci": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.0.2.tgz", "integrity": "sha512-Xc41mKvjouTXD3Oy9AqySz1IeyvJvHZ20Twf5ZLYbNpPPIuCnL/qHCmNlD01LoNy0JTunw9HPYVptD19Ac7Mbw==", "dev": true, "requires": { "execa": "^4.0.0", "java-properties": "^1.0.0" }, "dependencies": { "execa": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, "requires": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "human-signals": "^1.1.1", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } }, "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true } } }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, "escape-string-regexp": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-3.0.0.tgz", "integrity": "sha512-11dXIUC3umvzEViLP117d0KN6LJzZxh5+9F4E/7WLAAw7GrHk8NpUR+g9iJi/pe9C0py4F8rs0hreyRCwlAuZg==", "dev": true }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "requires": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" }, "dependencies": { "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true } } }, "fast-glob": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.6.tgz", "integrity": "sha512-GnLuqj/pvQ7pX8/L4J84nijv6sAnlwvSDpMkJi9i7nPmPxGtRPkBSStfvDW5l6nMdX9VWe+pkKWFTgD+vF2QSQ==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "fastq": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", "dev": true, "requires": { "reusify": "^1.0.4" } }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" }, "dependencies": { "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true } } }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { "to-regex-range": "^5.0.1" } }, "filter-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", "dev": true }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "find-versions": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", "dev": true, "requires": { "semver-regex": "^3.1.2" } }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" } }, "fs-extra": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^1.0.0" } }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "get-intrinsic": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1" } }, "get-stdin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", "dev": true }, "get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" } }, "git-log-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", "integrity": "sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo=", "dev": true, "requires": { "argv-formatter": "~1.0.0", "spawn-error-forwarder": "~1.0.0", "split2": "~1.0.0", "stream-combiner2": "~1.1.1", "through2": "~2.0.0", "traverse": "~0.6.6" }, "dependencies": { "split2": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", "integrity": "sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ=", "dev": true, "requires": { "through2": "~2.0.0" } }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } } } }, "git-raw-commits": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.8.tgz", "integrity": "sha512-6Gk7tQHGMLEL1bSnrMJTCVt2AQl4EmCcJDtzs/JJacCb2+TNEyHM67Gp7Ri9faF7OcGpjGGRjHLvs/AG7QKZ2Q==", "dev": true, "requires": { "dargs": "^7.0.0", "lodash.template": "^4.0.2", "meow": "^8.0.0", "split2": "^2.0.0", "through2": "^4.0.0" } }, "glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" } }, "global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "dev": true, "requires": { "ini": "^1.3.4" } }, "globby": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.1.1", "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" } }, "got": { "version": "10.7.0", "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", "integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==", "dev": true, "requires": { "@sindresorhus/is": "^2.0.0", "@szmarczak/http-timer": "^4.0.0", "@types/cacheable-request": "^6.0.1", "cacheable-lookup": "^2.0.0", "cacheable-request": "^7.0.1", "decompress-response": "^5.0.0", "duplexer3": "^0.1.4", "get-stream": "^5.0.0", "lowercase-keys": "^2.0.0", "mimic-response": "^2.1.0", "p-cancelable": "^2.0.0", "p-event": "^4.0.0", "responselike": "^2.0.0", "to-readable-stream": "^2.0.0", "type-fest": "^0.10.0" } }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, "handlebars": { "version": "4.7.7", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", "dev": true, "requires": { "minimist": "^1.2.5", "neo-async": "^2.6.0", "source-map": "^0.6.1", "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" } }, "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { "function-bind": "^1.1.1" } }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, "hook-std": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", "integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==", "dev": true }, "hosted-git-info": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", "dev": true }, "http-proxy-agent": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", "dev": true, "requires": { "@tootallnate/once": "1", "agent-base": "6", "debug": "4" } }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dev": true, "requires": { "agent-base": "6", "debug": "4" } }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, "ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, "import-fresh": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" }, "dependencies": { "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true } } }, "import-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", "dev": true, "requires": { "resolve-from": "^5.0.0" } }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, "into-stream": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", "dev": true, "requires": { "from2": "^2.3.0", "p-is-promise": "^3.0.0" } }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "is-core-module": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz", "integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==", "dev": true, "requires": { "has": "^1.0.3" } }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { "is-extglob": "^2.1.1" } }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, "is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "dev": true }, "is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", "dev": true }, "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true }, "is-ssh": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.3.3.tgz", "integrity": "sha512-NKzJmQzJfEEma3w5cJNcUMxoXfDjz0Zj0eyCalHn2E6VOwlzjZo0yuO2fcBSf8zhFuVCL/82/r5gRcoi6aEPVQ==", "dev": true, "requires": { "protocols": "^1.1.0" } }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, "is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", "dev": true, "requires": { "text-extensions": "^1.0.0" } }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "issue-parser": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", "dev": true, "requires": { "lodash.capitalize": "^4.2.1", "lodash.escaperegexp": "^4.1.2", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.uniqby": "^4.7.0" } }, "java-properties": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", "dev": true }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" }, "dependencies": { "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true } } }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, "keyv": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", "dev": true, "requires": { "json-buffer": "3.0.1" } }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", "pify": "^3.0.0", "strip-bom": "^3.0.0" }, "dependencies": { "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } } } }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { "p-locate": "^4.1.0" } }, "lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, "lodash.capitalize": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, "lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", "dev": true }, "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", "dev": true }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", "dev": true }, "lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", "dev": true }, "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", "dev": true, "requires": { "lodash._reinterpolate": "^3.0.0", "lodash.templatesettings": "^4.0.0" } }, "lodash.templatesettings": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", "dev": true, "requires": { "lodash._reinterpolate": "^3.0.0" } }, "lodash.toarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", "dev": true }, "lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", "dev": true }, "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", "dev": true }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "requires": { "yallist": "^4.0.0" } }, "map-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", "dev": true }, "marked": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", "dev": true }, "marked-terminal": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.1.1.tgz", "integrity": "sha512-t7Mdf6T3PvOEyN01c3tYxDzhyKZ8xnkp8Rs6Fohno63L/0pFTJ5Qtwto2AQVuDtbQiWzD+4E5AAu1Z2iLc8miQ==", "dev": true, "requires": { "ansi-escapes": "^4.3.1", "cardinal": "^2.1.1", "chalk": "^4.1.0", "cli-table": "^0.3.1", "node-emoji": "^1.10.0", "supports-hyperlinks": "^2.1.0" }, "dependencies": { "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" } }, "chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, "meow": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/meow/-/meow-8.0.0.tgz", "integrity": "sha512-nbsTRz2fwniJBFgUkcdISq8y/q9n9VbiHYbfwklFh5V4V2uAcxtKQkDc0yCLPM/kP0d+inZBewn3zJqewHE7kg==", "dev": true, "requires": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.18.0", "yargs-parser": "^20.2.3" }, "dependencies": { "type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", "dev": true } } }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" } }, "mime": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", "dev": true }, "mime-db": { "version": "1.48.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", "dev": true }, "mime-types": { "version": "2.1.31", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", "dev": true, "requires": { "mime-db": "1.48.0" } }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "mimic-response": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", "dev": true }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "minimist-options": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", "dev": true, "requires": { "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", "kind-of": "^6.0.3" } }, "modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, "nerf-dart": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", "dev": true }, "node-emoji": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", "dev": true, "requires": { "lodash.toarray": "^4.4.0" } }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", "dev": true }, "normalize-package-data": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", "dev": true, "requires": { "hosted-git-info": "^3.0.6", "resolve": "^1.17.0", "semver": "^7.3.2", "validate-npm-package-license": "^3.0.1" } }, "normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", "dev": true }, "npm": { "version": "7.19.0", "resolved": "https://registry.npmjs.org/npm/-/npm-7.19.0.tgz", "integrity": "sha512-bak/ZycWS8kalVFJ2m1JEo2pgvSwEDA9FMGthGnufAKciu4X4kpJMDZKe8VZKNaA/cXehd9XI7QdSZXmZauR8Q==", "dev": true, "requires": { "@npmcli/arborist": "^2.6.4", "@npmcli/ci-detect": "^1.2.0", "@npmcli/config": "^2.2.0", "@npmcli/package-json": "^1.0.1", "@npmcli/run-script": "^1.8.5", "abbrev": "~1.1.1", "ansicolors": "~0.3.2", "ansistyles": "~0.1.3", "archy": "~1.0.0", "byte-size": "^7.0.1", "cacache": "^15.2.0", "chalk": "^4.1.0", "chownr": "^2.0.0", "cli-columns": "^3.1.2", "cli-table3": "^0.6.0", "columnify": "~1.5.4", "glob": "^7.1.7", "graceful-fs": "^4.2.6", "hosted-git-info": "^4.0.2", "ini": "^2.0.0", "init-package-json": "^2.0.3", "is-cidr": "^4.0.2", "json-parse-even-better-errors": "^2.3.1", "leven": "^3.1.0", "libnpmaccess": "^4.0.2", "libnpmdiff": "^2.0.4", "libnpmexec": "^2.0.0", "libnpmfund": "^1.1.0", "libnpmhook": "^6.0.2", "libnpmorg": "^2.0.2", "libnpmpack": "^2.0.1", "libnpmpublish": "^4.0.1", "libnpmsearch": "^3.1.1", "libnpmteam": "^2.0.3", "libnpmversion": "^1.2.1", "make-fetch-happen": "^9.0.3", "minipass": "^3.1.3", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", "mkdirp-infer-owner": "^2.0.0", "ms": "^2.1.2", "node-gyp": "^7.1.2", "nopt": "^5.0.0", "npm-audit-report": "^2.1.5", "npm-package-arg": "^8.1.5", "npm-pick-manifest": "^6.1.1", "npm-profile": "^5.0.3", "npm-registry-fetch": "^11.0.0", "npm-user-validate": "^1.0.1", "npmlog": "~4.1.2", "opener": "^1.5.2", "pacote": "^11.3.3", "parse-conflict-json": "^1.1.1", "qrcode-terminal": "^0.12.0", "read": "~1.0.7", "read-package-json": "^3.0.1", "read-package-json-fast": "^2.0.2", "readdir-scoped-modules": "^1.1.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "ssri": "^8.0.1", "tar": "^6.1.0", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^1.0.4", "validate-npm-package-name": "~3.0.0", "which": "^2.0.2", "write-file-atomic": "^3.0.3" }, "dependencies": { "@npmcli/arborist": { "version": "2.6.4", "bundled": true, "dev": true, "requires": { "@npmcli/installed-package-contents": "^1.0.7", "@npmcli/map-workspaces": "^1.0.2", "@npmcli/metavuln-calculator": "^1.1.0", "@npmcli/move-file": "^1.1.0", "@npmcli/name-from-folder": "^1.0.1", "@npmcli/node-gyp": "^1.0.1", "@npmcli/package-json": "^1.0.1", "@npmcli/run-script": "^1.8.2", "bin-links": "^2.2.1", "cacache": "^15.0.3", "common-ancestor-path": "^1.0.1", "json-parse-even-better-errors": "^2.3.1", "json-stringify-nice": "^1.1.4", "mkdirp-infer-owner": "^2.0.0", "npm-install-checks": "^4.0.0", "npm-package-arg": "^8.1.0", "npm-pick-manifest": "^6.1.0", "npm-registry-fetch": "^11.0.0", "pacote": "^11.2.6", "parse-conflict-json": "^1.1.1", "proc-log": "^1.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^1.0.1", "read-package-json-fast": "^2.0.2", "readdir-scoped-modules": "^1.1.0", "semver": "^7.3.5", "tar": "^6.1.0", "treeverse": "^1.0.4", "walk-up-path": "^1.0.0" } }, "@npmcli/ci-detect": { "version": "1.3.0", "bundled": true, "dev": true }, "@npmcli/config": { "version": "2.2.0", "bundled": true, "dev": true, "requires": { "ini": "^2.0.0", "mkdirp-infer-owner": "^2.0.0", "nopt": "^5.0.0", "semver": "^7.3.4", "walk-up-path": "^1.0.0" } }, "@npmcli/disparity-colors": { "version": "1.0.1", "bundled": true, "dev": true, "requires": { "ansi-styles": "^4.3.0" } }, "@npmcli/git": { "version": "2.0.9", "bundled": true, "dev": true, "requires": { "@npmcli/promise-spawn": "^1.3.2", "lru-cache": "^6.0.0", "mkdirp": "^1.0.4", "npm-pick-manifest": "^6.1.1", "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^2.0.2" } }, "@npmcli/installed-package-contents": { "version": "1.0.7", "bundled": true, "dev": true, "requires": { "npm-bundled": "^1.1.1", "npm-normalize-package-bin": "^1.0.1" } }, "@npmcli/map-workspaces": { "version": "1.0.3", "bundled": true, "dev": true, "requires": { "@npmcli/name-from-folder": "^1.0.1", "glob": "^7.1.6", "minimatch": "^3.0.4", "read-package-json-fast": "^2.0.1" } }, "@npmcli/metavuln-calculator": { "version": "1.1.1", "bundled": true, "dev": true, "requires": { "cacache": "^15.0.5", "pacote": "^11.1.11", "semver": "^7.3.2" } }, "@npmcli/move-file": { "version": "1.1.2", "bundled": true, "dev": true, "requires": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "@npmcli/name-from-folder": { "version": "1.0.1", "bundled": true, "dev": true }, "@npmcli/node-gyp": { "version": "1.0.2", "bundled": true, "dev": true }, "@npmcli/package-json": { "version": "1.0.1", "bundled": true, "dev": true, "requires": { "json-parse-even-better-errors": "^2.3.1" } }, "@npmcli/promise-spawn": { "version": "1.3.2", "bundled": true, "dev": true, "requires": { "infer-owner": "^1.0.4" } }, "@npmcli/run-script": { "version": "1.8.5", "bundled": true, "dev": true, "requires": { "@npmcli/node-gyp": "^1.0.2", "@npmcli/promise-spawn": "^1.3.2", "infer-owner": "^1.0.4", "node-gyp": "^7.1.0", "read-package-json-fast": "^2.0.1" } }, "@tootallnate/once": { "version": "1.1.2", "bundled": true, "dev": true }, "abbrev": { "version": "1.1.1", "bundled": true, "dev": true }, "agent-base": { "version": "6.0.2", "bundled": true, "dev": true, "requires": { "debug": "4" } }, "agentkeepalive": { "version": "4.1.4", "bundled": true, "dev": true, "requires": { "debug": "^4.1.0", "depd": "^1.1.2", "humanize-ms": "^1.2.1" } }, "aggregate-error": { "version": "3.1.0", "bundled": true, "dev": true, "requires": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "ajv": { "version": "6.12.6", "bundled": true, "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "ansi-regex": { "version": "2.1.1", "bundled": true, "dev": true }, "ansi-styles": { "version": "4.3.0", "bundled": true, "dev": true, "requires": { "color-convert": "^2.0.1" } }, "ansicolors": { "version": "0.3.2", "bundled": true, "dev": true }, "ansistyles": { "version": "0.1.3", "bundled": true, "dev": true }, "aproba": { "version": "2.0.0", "bundled": true, "dev": true }, "archy": { "version": "1.0.0", "bundled": true, "dev": true }, "are-we-there-yet": { "version": "1.1.5", "bundled": true, "dev": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" } }, "asap": { "version": "2.0.6", "bundled": true, "dev": true }, "asn1": { "version": "0.2.4", "bundled": true, "dev": true, "requires": { "safer-buffer": "~2.1.0" } }, "assert-plus": { "version": "1.0.0", "bundled": true, "dev": true }, "asynckit": { "version": "0.4.0", "bundled": true, "dev": true }, "aws-sign2": { "version": "0.7.0", "bundled": true, "dev": true }, "aws4": { "version": "1.11.0", "bundled": true, "dev": true }, "balanced-match": { "version": "1.0.2", "bundled": true, "dev": true }, "bcrypt-pbkdf": { "version": "1.0.2", "bundled": true, "dev": true, "requires": { "tweetnacl": "^0.14.3" } }, "bin-links": { "version": "2.2.1", "bundled": true, "dev": true, "requires": { "cmd-shim": "^4.0.1", "mkdirp": "^1.0.3", "npm-normalize-package-bin": "^1.0.0", "read-cmd-shim": "^2.0.0", "rimraf": "^3.0.0", "write-file-atomic": "^3.0.3" } }, "binary-extensions": { "version": "2.2.0", "bundled": true, "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "builtins": { "version": "1.0.3", "bundled": true, "dev": true }, "byte-size": { "version": "7.0.1", "bundled": true, "dev": true }, "cacache": { "version": "15.2.0", "bundled": true, "dev": true, "requires": { "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "caseless": { "version": "0.12.0", "bundled": true, "dev": true }, "chalk": { "version": "4.1.1", "bundled": true, "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "chownr": { "version": "2.0.0", "bundled": true, "dev": true }, "cidr-regex": { "version": "3.1.1", "bundled": true, "dev": true, "requires": { "ip-regex": "^4.1.0" } }, "clean-stack": { "version": "2.2.0", "bundled": true, "dev": true }, "cli-columns": { "version": "3.1.2", "bundled": true, "dev": true, "requires": { "string-width": "^2.0.0", "strip-ansi": "^3.0.1" } }, "cli-table3": { "version": "0.6.0", "bundled": true, "dev": true, "requires": { "colors": "^1.1.2", "object-assign": "^4.1.0", "string-width": "^4.2.0" }, "dependencies": { "ansi-regex": { "version": "5.0.0", "bundled": true, "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "bundled": true, "dev": true }, "string-width": { "version": "4.2.2", "bundled": true, "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } }, "strip-ansi": { "version": "6.0.0", "bundled": true, "dev": true, "requires": { "ansi-regex": "^5.0.0" } } } }, "clone": { "version": "1.0.4", "bundled": true, "dev": true }, "cmd-shim": { "version": "4.1.0", "bundled": true, "dev": true, "requires": { "mkdirp-infer-owner": "^2.0.0" } }, "code-point-at": { "version": "1.1.0", "bundled": true, "dev": true }, "color-convert": { "version": "2.0.1", "bundled": true, "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "bundled": true, "dev": true }, "colors": { "version": "1.4.0", "bundled": true, "dev": true, "optional": true }, "columnify": { "version": "1.5.4", "bundled": true, "dev": true, "requires": { "strip-ansi": "^3.0.0", "wcwidth": "^1.0.0" } }, "combined-stream": { "version": "1.0.8", "bundled": true, "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, "common-ancestor-path": { "version": "1.0.1", "bundled": true, "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, "dev": true }, "core-util-is": { "version": "1.0.2", "bundled": true, "dev": true }, "dashdash": { "version": "1.14.1", "bundled": true, "dev": true, "requires": { "assert-plus": "^1.0.0" } }, "debug": { "version": "4.3.1", "bundled": true, "dev": true, "requires": { "ms": "2.1.2" }, "dependencies": { "ms": { "version": "2.1.2", "bundled": true, "dev": true } } }, "debuglog": { "version": "1.0.1", "bundled": true, "dev": true }, "defaults": { "version": "1.0.3", "bundled": true, "dev": true, "requires": { "clone": "^1.0.2" } }, "delayed-stream": { "version": "1.0.0", "bundled": true, "dev": true }, "delegates": { "version": "1.0.0", "bundled": true, "dev": true }, "depd": { "version": "1.1.2", "bundled": true, "dev": true }, "dezalgo": { "version": "1.0.3", "bundled": true, "dev": true, "requires": { "asap": "^2.0.0", "wrappy": "1" } }, "diff": { "version": "5.0.0", "bundled": true, "dev": true }, "ecc-jsbn": { "version": "0.1.2", "bundled": true, "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, "emoji-regex": { "version": "8.0.0", "bundled": true, "dev": true }, "encoding": { "version": "0.1.13", "bundled": true, "dev": true, "optional": true, "requires": { "iconv-lite": "^0.6.2" } }, "env-paths": { "version": "2.2.1", "bundled": true, "dev": true }, "err-code": { "version": "2.0.3", "bundled": true, "dev": true }, "extend": { "version": "3.0.2", "bundled": true, "dev": true }, "extsprintf": { "version": "1.3.0", "bundled": true, "dev": true }, "fast-deep-equal": { "version": "3.1.3", "bundled": true, "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", "bundled": true, "dev": true }, "forever-agent": { "version": "0.6.1", "bundled": true, "dev": true }, "fs-minipass": { "version": "2.1.0", "bundled": true, "dev": true, "requires": { "minipass": "^3.0.0" } }, "fs.realpath": { "version": "1.0.0", "bundled": true, "dev": true }, "function-bind": { "version": "1.1.1", "bundled": true, "dev": true }, "gauge": { "version": "2.7.4", "bundled": true, "dev": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", "has-unicode": "^2.0.0", "object-assign": "^4.1.0", "signal-exit": "^3.0.0", "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" }, "dependencies": { "aproba": { "version": "1.2.0", "bundled": true, "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, "dev": true, "requires": { "number-is-nan": "^1.0.0" } }, "string-width": { "version": "1.0.2", "bundled": true, "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } } } }, "getpass": { "version": "0.1.7", "bundled": true, "dev": true, "requires": { "assert-plus": "^1.0.0" } }, "glob": { "version": "7.1.7", "bundled": true, "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "graceful-fs": { "version": "4.2.6", "bundled": true, "dev": true }, "har-schema": { "version": "2.0.0", "bundled": true, "dev": true }, "har-validator": { "version": "5.1.5", "bundled": true, "dev": true, "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, "has": { "version": "1.0.3", "bundled": true, "dev": true, "requires": { "function-bind": "^1.1.1" } }, "has-flag": { "version": "4.0.0", "bundled": true, "dev": true }, "has-unicode": { "version": "2.0.1", "bundled": true, "dev": true }, "hosted-git-info": { "version": "4.0.2", "bundled": true, "dev": true, "requires": { "lru-cache": "^6.0.0" } }, "http-cache-semantics": { "version": "4.1.0", "bundled": true, "dev": true }, "http-proxy-agent": { "version": "4.0.1", "bundled": true, "dev": true, "requires": { "@tootallnate/once": "1", "agent-base": "6", "debug": "4" } }, "http-signature": { "version": "1.2.0", "bundled": true, "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } }, "https-proxy-agent": { "version": "5.0.0", "bundled": true, "dev": true, "requires": { "agent-base": "6", "debug": "4" } }, "humanize-ms": { "version": "1.2.1", "bundled": true, "dev": true, "requires": { "ms": "^2.0.0" } }, "iconv-lite": { "version": "0.6.3", "bundled": true, "dev": true, "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "ignore-walk": { "version": "3.0.4", "bundled": true, "dev": true, "requires": { "minimatch": "^3.0.4" } }, "imurmurhash": { "version": "0.1.4", "bundled": true, "dev": true }, "indent-string": { "version": "4.0.0", "bundled": true, "dev": true }, "infer-owner": { "version": "1.0.4", "bundled": true, "dev": true }, "inflight": { "version": "1.0.6", "bundled": true, "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { "version": "2.0.4", "bundled": true, "dev": true }, "ini": { "version": "2.0.0", "bundled": true, "dev": true }, "init-package-json": { "version": "2.0.3", "bundled": true, "dev": true, "requires": { "glob": "^7.1.1", "npm-package-arg": "^8.1.2", "promzard": "^0.3.0", "read": "~1.0.1", "read-package-json": "^3.0.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^3.0.0" } }, "ip": { "version": "1.1.5", "bundled": true, "dev": true }, "ip-regex": { "version": "4.3.0", "bundled": true, "dev": true }, "is-cidr": { "version": "4.0.2", "bundled": true, "dev": true, "requires": { "cidr-regex": "^3.1.1" } }, "is-core-module": { "version": "2.4.0", "bundled": true, "dev": true, "requires": { "has": "^1.0.3" } }, "is-fullwidth-code-point": { "version": "2.0.0", "bundled": true, "dev": true }, "is-lambda": { "version": "1.0.1", "bundled": true, "dev": true }, "is-typedarray": { "version": "1.0.0", "bundled": true, "dev": true }, "isarray": { "version": "1.0.0", "bundled": true, "dev": true }, "isexe": { "version": "2.0.0", "bundled": true, "dev": true }, "isstream": { "version": "0.1.2", "bundled": true, "dev": true }, "jsbn": { "version": "0.1.1", "bundled": true, "dev": true }, "json-parse-even-better-errors": { "version": "2.3.1", "bundled": true, "dev": true }, "json-schema": { "version": "0.2.3", "bundled": true, "dev": true }, "json-schema-traverse": { "version": "0.4.1", "bundled": true, "dev": true }, "json-stringify-nice": { "version": "1.1.4", "bundled": true, "dev": true }, "json-stringify-safe": { "version": "5.0.1", "bundled": true, "dev": true }, "jsonparse": { "version": "1.3.1", "bundled": true, "dev": true }, "jsprim": { "version": "1.4.1", "bundled": true, "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" } }, "just-diff": { "version": "3.1.1", "bundled": true, "dev": true }, "just-diff-apply": { "version": "3.0.0", "bundled": true, "dev": true }, "leven": { "version": "3.1.0", "bundled": true, "dev": true }, "libnpmaccess": { "version": "4.0.3", "bundled": true, "dev": true, "requires": { "aproba": "^2.0.0", "minipass": "^3.1.1", "npm-package-arg": "^8.1.2", "npm-registry-fetch": "^11.0.0" } }, "libnpmdiff": { "version": "2.0.4", "bundled": true, "dev": true, "requires": { "@npmcli/disparity-colors": "^1.0.1", "@npmcli/installed-package-contents": "^1.0.7", "binary-extensions": "^2.2.0", "diff": "^5.0.0", "minimatch": "^3.0.4", "npm-package-arg": "^8.1.4", "pacote": "^11.3.4", "tar": "^6.1.0" } }, "libnpmexec": { "version": "2.0.0", "bundled": true, "dev": true, "requires": { "@npmcli/arborist": "^2.3.0", "@npmcli/ci-detect": "^1.3.0", "@npmcli/run-script": "^1.8.4", "chalk": "^4.1.0", "mkdirp-infer-owner": "^2.0.0", "npm-package-arg": "^8.1.2", "pacote": "^11.3.1", "proc-log": "^1.0.0", "read": "^1.0.7", "read-package-json-fast": "^2.0.2", "walk-up-path": "^1.0.0" } }, "libnpmfund": { "version": "1.1.0", "bundled": true, "dev": true, "requires": { "@npmcli/arborist": "^2.5.0" } }, "libnpmhook": { "version": "6.0.3", "bundled": true, "dev": true, "requires": { "aproba": "^2.0.0", "npm-registry-fetch": "^11.0.0" } }, "libnpmorg": { "version": "2.0.3", "bundled": true, "dev": true, "requires": { "aproba": "^2.0.0", "npm-registry-fetch": "^11.0.0" } }, "libnpmpack": { "version": "2.0.1", "bundled": true, "dev": true, "requires": { "@npmcli/run-script": "^1.8.3", "npm-package-arg": "^8.1.0", "pacote": "^11.2.6" } }, "libnpmpublish": { "version": "4.0.2", "bundled": true, "dev": true, "requires": { "normalize-package-data": "^3.0.2", "npm-package-arg": "^8.1.2", "npm-registry-fetch": "^11.0.0", "semver": "^7.1.3", "ssri": "^8.0.1" } }, "libnpmsearch": { "version": "3.1.2", "bundled": true, "dev": true, "requires": { "npm-registry-fetch": "^11.0.0" } }, "libnpmteam": { "version": "2.0.4", "bundled": true, "dev": true, "requires": { "aproba": "^2.0.0", "npm-registry-fetch": "^11.0.0" } }, "libnpmversion": { "version": "1.2.1", "bundled": true, "dev": true, "requires": { "@npmcli/git": "^2.0.7", "@npmcli/run-script": "^1.8.4", "json-parse-even-better-errors": "^2.3.1", "semver": "^7.3.5", "stringify-package": "^1.0.1" } }, "lru-cache": { "version": "6.0.0", "bundled": true, "dev": true, "requires": { "yallist": "^4.0.0" } }, "make-fetch-happen": { "version": "9.0.3", "bundled": true, "dev": true, "requires": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^5.0.0", "ssri": "^8.0.0" } }, "mime-db": { "version": "1.48.0", "bundled": true, "dev": true }, "mime-types": { "version": "2.1.31", "bundled": true, "dev": true, "requires": { "mime-db": "1.48.0" } }, "minimatch": { "version": "3.0.4", "bundled": true, "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, "minipass": { "version": "3.1.3", "bundled": true, "dev": true, "requires": { "yallist": "^4.0.0" } }, "minipass-collect": { "version": "1.0.2", "bundled": true, "dev": true, "requires": { "minipass": "^3.0.0" } }, "minipass-fetch": { "version": "1.3.3", "bundled": true, "dev": true, "requires": { "encoding": "^0.1.12", "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" } }, "minipass-flush": { "version": "1.0.5", "bundled": true, "dev": true, "requires": { "minipass": "^3.0.0" } }, "minipass-json-stream": { "version": "1.0.1", "bundled": true, "dev": true, "requires": { "jsonparse": "^1.3.1", "minipass": "^3.0.0" } }, "minipass-pipeline": { "version": "1.2.4", "bundled": true, "dev": true, "requires": { "minipass": "^3.0.0" } }, "minipass-sized": { "version": "1.0.3", "bundled": true, "dev": true, "requires": { "minipass": "^3.0.0" } }, "minizlib": { "version": "2.1.2", "bundled": true, "dev": true, "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "mkdirp": { "version": "1.0.4", "bundled": true, "dev": true }, "mkdirp-infer-owner": { "version": "2.0.0", "bundled": true, "dev": true, "requires": { "chownr": "^2.0.0", "infer-owner": "^1.0.4", "mkdirp": "^1.0.3" } }, "ms": { "version": "2.1.3", "bundled": true, "dev": true }, "mute-stream": { "version": "0.0.8", "bundled": true, "dev": true }, "negotiator": { "version": "0.6.2", "bundled": true, "dev": true }, "node-gyp": { "version": "7.1.2", "bundled": true, "dev": true, "requires": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.3", "nopt": "^5.0.0", "npmlog": "^4.1.2", "request": "^2.88.2", "rimraf": "^3.0.2", "semver": "^7.3.2", "tar": "^6.0.2", "which": "^2.0.2" } }, "nopt": { "version": "5.0.0", "bundled": true, "dev": true, "requires": { "abbrev": "1" } }, "normalize-package-data": { "version": "3.0.2", "bundled": true, "dev": true, "requires": { "hosted-git-info": "^4.0.1", "resolve": "^1.20.0", "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" } }, "npm-audit-report": { "version": "2.1.5", "bundled": true, "dev": true, "requires": { "chalk": "^4.0.0" } }, "npm-bundled": { "version": "1.1.2", "bundled": true, "dev": true, "requires": { "npm-normalize-package-bin": "^1.0.1" } }, "npm-install-checks": { "version": "4.0.0", "bundled": true, "dev": true, "requires": { "semver": "^7.1.1" } }, "npm-normalize-package-bin": { "version": "1.0.1", "bundled": true, "dev": true }, "npm-package-arg": { "version": "8.1.5", "bundled": true, "dev": true, "requires": { "hosted-git-info": "^4.0.1", "semver": "^7.3.4", "validate-npm-package-name": "^3.0.0" } }, "npm-packlist": { "version": "2.2.2", "bundled": true, "dev": true, "requires": { "glob": "^7.1.6", "ignore-walk": "^3.0.3", "npm-bundled": "^1.1.1", "npm-normalize-package-bin": "^1.0.1" } }, "npm-pick-manifest": { "version": "6.1.1", "bundled": true, "dev": true, "requires": { "npm-install-checks": "^4.0.0", "npm-normalize-package-bin": "^1.0.1", "npm-package-arg": "^8.1.2", "semver": "^7.3.4" } }, "npm-profile": { "version": "5.0.4", "bundled": true, "dev": true, "requires": { "npm-registry-fetch": "^11.0.0" } }, "npm-registry-fetch": { "version": "11.0.0", "bundled": true, "dev": true, "requires": { "make-fetch-happen": "^9.0.1", "minipass": "^3.1.3", "minipass-fetch": "^1.3.0", "minipass-json-stream": "^1.0.1", "minizlib": "^2.0.0", "npm-package-arg": "^8.0.0" } }, "npm-user-validate": { "version": "1.0.1", "bundled": true, "dev": true }, "npmlog": { "version": "4.1.2", "bundled": true, "dev": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", "gauge": "~2.7.3", "set-blocking": "~2.0.0" } }, "number-is-nan": { "version": "1.0.1", "bundled": true, "dev": true }, "oauth-sign": { "version": "0.9.0", "bundled": true, "dev": true }, "object-assign": { "version": "4.1.1", "bundled": true, "dev": true }, "once": { "version": "1.4.0", "bundled": true, "dev": true, "requires": { "wrappy": "1" } }, "opener": { "version": "1.5.2", "bundled": true, "dev": true }, "p-map": { "version": "4.0.0", "bundled": true, "dev": true, "requires": { "aggregate-error": "^3.0.0" } }, "pacote": { "version": "11.3.4", "bundled": true, "dev": true, "requires": { "@npmcli/git": "^2.0.1", "@npmcli/installed-package-contents": "^1.0.6", "@npmcli/promise-spawn": "^1.2.0", "@npmcli/run-script": "^1.8.2", "cacache": "^15.0.5", "chownr": "^2.0.0", "fs-minipass": "^2.1.0", "infer-owner": "^1.0.4", "minipass": "^3.1.3", "mkdirp": "^1.0.3", "npm-package-arg": "^8.0.1", "npm-packlist": "^2.1.4", "npm-pick-manifest": "^6.0.0", "npm-registry-fetch": "^11.0.0", "promise-retry": "^2.0.1", "read-package-json-fast": "^2.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.1.0" } }, "parse-conflict-json": { "version": "1.1.1", "bundled": true, "dev": true, "requires": { "json-parse-even-better-errors": "^2.3.0", "just-diff": "^3.0.1", "just-diff-apply": "^3.0.0" } }, "path-is-absolute": { "version": "1.0.1", "bundled": true, "dev": true }, "path-parse": { "version": "1.0.7", "bundled": true, "dev": true }, "performance-now": { "version": "2.1.0", "bundled": true, "dev": true }, "proc-log": { "version": "1.0.0", "bundled": true, "dev": true }, "process-nextick-args": { "version": "2.0.1", "bundled": true, "dev": true }, "promise-all-reject-late": { "version": "1.0.1", "bundled": true, "dev": true }, "promise-call-limit": { "version": "1.0.1", "bundled": true, "dev": true }, "promise-inflight": { "version": "1.0.1", "bundled": true, "dev": true }, "promise-retry": { "version": "2.0.1", "bundled": true, "dev": true, "requires": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "promzard": { "version": "0.3.0", "bundled": true, "dev": true, "requires": { "read": "1" } }, "psl": { "version": "1.8.0", "bundled": true, "dev": true }, "punycode": { "version": "2.1.1", "bundled": true, "dev": true }, "qrcode-terminal": { "version": "0.12.0", "bundled": true, "dev": true }, "qs": { "version": "6.5.2", "bundled": true, "dev": true }, "read": { "version": "1.0.7", "bundled": true, "dev": true, "requires": { "mute-stream": "~0.0.4" } }, "read-cmd-shim": { "version": "2.0.0", "bundled": true, "dev": true }, "read-package-json": { "version": "3.0.1", "bundled": true, "dev": true, "requires": { "glob": "^7.1.1", "json-parse-even-better-errors": "^2.3.0", "normalize-package-data": "^3.0.0", "npm-normalize-package-bin": "^1.0.0" } }, "read-package-json-fast": { "version": "2.0.2", "bundled": true, "dev": true, "requires": { "json-parse-even-better-errors": "^2.3.0", "npm-normalize-package-bin": "^1.0.1" } }, "readable-stream": { "version": "2.3.7", "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "readdir-scoped-modules": { "version": "1.1.0", "bundled": true, "dev": true, "requires": { "debuglog": "^1.0.1", "dezalgo": "^1.0.0", "graceful-fs": "^4.1.2", "once": "^1.3.0" } }, "request": { "version": "2.88.2", "bundled": true, "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, "dependencies": { "form-data": { "version": "2.3.3", "bundled": true, "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, "tough-cookie": { "version": "2.5.0", "bundled": true, "dev": true, "requires": { "psl": "^1.1.28", "punycode": "^2.1.1" } } } }, "resolve": { "version": "1.20.0", "bundled": true, "dev": true, "requires": { "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, "retry": { "version": "0.12.0", "bundled": true, "dev": true }, "rimraf": { "version": "3.0.2", "bundled": true, "dev": true, "requires": { "glob": "^7.1.3" } }, "safe-buffer": { "version": "5.1.2", "bundled": true, "dev": true }, "safer-buffer": { "version": "2.1.2", "bundled": true, "dev": true }, "semver": { "version": "7.3.5", "bundled": true, "dev": true, "requires": { "lru-cache": "^6.0.0" } }, "set-blocking": { "version": "2.0.0", "bundled": true, "dev": true }, "signal-exit": { "version": "3.0.3", "bundled": true, "dev": true }, "smart-buffer": { "version": "4.1.0", "bundled": true, "dev": true }, "socks": { "version": "2.6.1", "bundled": true, "dev": true, "requires": { "ip": "^1.1.5", "smart-buffer": "^4.1.0" } }, "socks-proxy-agent": { "version": "5.0.0", "bundled": true, "dev": true, "requires": { "agent-base": "6", "debug": "4", "socks": "^2.3.3" } }, "spdx-correct": { "version": "3.1.1", "bundled": true, "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { "version": "2.3.0", "bundled": true, "dev": true }, "spdx-expression-parse": { "version": "3.0.1", "bundled": true, "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { "version": "3.0.9", "bundled": true, "dev": true }, "sshpk": { "version": "1.16.1", "bundled": true, "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" } }, "ssri": { "version": "8.0.1", "bundled": true, "dev": true, "requires": { "minipass": "^3.1.1" } }, "string-width": { "version": "2.1.1", "bundled": true, "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" }, "dependencies": { "ansi-regex": { "version": "3.0.0", "bundled": true, "dev": true }, "strip-ansi": { "version": "4.0.0", "bundled": true, "dev": true, "requires": { "ansi-regex": "^3.0.0" } } } }, "string_decoder": { "version": "1.1.1", "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" } }, "stringify-package": { "version": "1.0.1", "bundled": true, "dev": true }, "strip-ansi": { "version": "3.0.1", "bundled": true, "dev": true, "requires": { "ansi-regex": "^2.0.0" } }, "supports-color": { "version": "7.2.0", "bundled": true, "dev": true, "requires": { "has-flag": "^4.0.0" } }, "tar": { "version": "6.1.0", "bundled": true, "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^3.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "text-table": { "version": "0.2.0", "bundled": true, "dev": true }, "tiny-relative-date": { "version": "1.3.0", "bundled": true, "dev": true }, "treeverse": { "version": "1.0.4", "bundled": true, "dev": true }, "tunnel-agent": { "version": "0.6.0", "bundled": true, "dev": true, "requires": { "safe-buffer": "^5.0.1" } }, "tweetnacl": { "version": "0.14.5", "bundled": true, "dev": true }, "typedarray-to-buffer": { "version": "3.1.5", "bundled": true, "dev": true, "requires": { "is-typedarray": "^1.0.0" } }, "unique-filename": { "version": "1.1.1", "bundled": true, "dev": true, "requires": { "unique-slug": "^2.0.0" } }, "unique-slug": { "version": "2.0.2", "bundled": true, "dev": true, "requires": { "imurmurhash": "^0.1.4" } }, "uri-js": { "version": "4.4.1", "bundled": true, "dev": true, "requires": { "punycode": "^2.1.0" } }, "util-deprecate": { "version": "1.0.2", "bundled": true, "dev": true }, "uuid": { "version": "3.4.0", "bundled": true, "dev": true }, "validate-npm-package-license": { "version": "3.0.4", "bundled": true, "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "validate-npm-package-name": { "version": "3.0.0", "bundled": true, "dev": true, "requires": { "builtins": "^1.0.3" } }, "verror": { "version": "1.10.0", "bundled": true, "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "walk-up-path": { "version": "1.0.0", "bundled": true, "dev": true }, "wcwidth": { "version": "1.0.1", "bundled": true, "dev": true, "requires": { "defaults": "^1.0.3" } }, "which": { "version": "2.0.2", "bundled": true, "dev": true, "requires": { "isexe": "^2.0.0" } }, "wide-align": { "version": "1.1.3", "bundled": true, "dev": true, "requires": { "string-width": "^1.0.2 || 2" } }, "wrappy": { "version": "1.0.2", "bundled": true, "dev": true }, "write-file-atomic": { "version": "3.0.3", "bundled": true, "dev": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, "yallist": { "version": "4.0.0", "bundled": true, "dev": true } } }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { "path-key": "^3.0.0" } }, "object-inspect": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", "dev": true }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" } }, "onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "requires": { "mimic-fn": "^2.1.0" } }, "p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "dev": true }, "p-each-series": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", "dev": true }, "p-event": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", "dev": true, "requires": { "p-timeout": "^3.1.0" } }, "p-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", "dev": true, "requires": { "p-map": "^2.0.0" } }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, "p-is-promise": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", "dev": true }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { "p-try": "^2.0.0" } }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { "p-limit": "^2.2.0" } }, "p-map": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "dev": true }, "p-reduce": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", "dev": true }, "p-retry": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.0.tgz", "integrity": "sha512-SAHbQEwg3X5DRNaLmWjT+DlGc93ba5i+aP3QLfVNDncQEQO4xjbYW4N/lcVTSuP0aJietGfx2t94dJLzfBMpXw==", "dev": true, "requires": { "@types/retry": "^0.12.0", "retry": "^0.13.1" } }, "p-timeout": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", "dev": true, "requires": { "p-finally": "^1.0.0" } }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { "callsites": "^3.0.0" } }, "parse-json": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "parse-path": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.3.tgz", "integrity": "sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA==", "dev": true, "requires": { "is-ssh": "^1.3.0", "protocols": "^1.4.0", "qs": "^6.9.4", "query-string": "^6.13.8" } }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "pkg-conf": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", "dev": true, "requires": { "find-up": "^2.0.0", "load-json-file": "^4.0.0" }, "dependencies": { "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { "locate-path": "^2.0.0" } }, "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" } }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { "p-try": "^1.0.0" } }, "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { "p-limit": "^1.1.0" } }, "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true } } }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, "protocols": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz", "integrity": "sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==", "dev": true }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", "dev": true }, "qs": { "version": "6.10.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", "dev": true, "requires": { "side-channel": "^1.0.4" } }, "query-string": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz", "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==", "dev": true, "requires": { "decode-uri-component": "^0.2.0", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, "quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" } }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^2.5.0", "parse-json": "^5.0.0", "type-fest": "^0.6.0" }, "dependencies": { "hosted-git-info": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", "dev": true } } }, "read-pkg-up": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { "find-up": "^4.1.0", "read-pkg": "^5.2.0", "type-fest": "^0.8.1" }, "dependencies": { "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true } } }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "requires": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "redeyed": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", "dev": true, "requires": { "esprima": "~4.0.0" } }, "regenerator-runtime": { "version": "0.13.7", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true }, "registry-auth-token": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", "dev": true, "requires": { "rc": "^1.2.8" } }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, "resolve": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, "requires": { "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, "resolve-global": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", "dev": true, "requires": { "global-dirs": "^0.1.1" } }, "responselike": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", "dev": true, "requires": { "lowercase-keys": "^2.0.0" } }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" } }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "requires": { "queue-microtask": "^1.2.2" } }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "semantic-release": { "version": "17.4.4", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.4.4.tgz", "integrity": "sha512-fQIA0lw2Sy/9+TcoM/BxyzKCSwdUd8EPRwGoOuBLgxKigPCY6kaKs8TOsgUVy6QrlTYwni2yzbMb5Q2107P9eA==", "dev": true, "requires": { "@semantic-release/commit-analyzer": "^8.0.0", "@semantic-release/error": "^2.2.0", "@semantic-release/github": "^7.0.0", "@semantic-release/npm": "^7.0.0", "@semantic-release/release-notes-generator": "^9.0.0", "aggregate-error": "^3.0.0", "cosmiconfig": "^7.0.0", "debug": "^4.0.0", "env-ci": "^5.0.0", "execa": "^5.0.0", "figures": "^3.0.0", "find-versions": "^4.0.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", "hook-std": "^2.0.0", "hosted-git-info": "^4.0.0", "lodash": "^4.17.21", "marked": "^2.0.0", "marked-terminal": "^4.1.1", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", "p-reduce": "^2.0.0", "read-pkg-up": "^7.0.0", "resolve-from": "^5.0.0", "semver": "^7.3.2", "semver-diff": "^3.1.1", "signale": "^1.2.1", "yargs": "^16.2.0" }, "dependencies": { "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" } }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, "hosted-git-info": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } } } }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true }, "semver-diff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", "dev": true, "requires": { "semver": "^6.3.0" }, "dependencies": { "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } }, "semver-regex": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz", "integrity": "sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==", "dev": true }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "requires": { "shebang-regex": "^3.0.0" } }, "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, "requires": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", "object-inspect": "^1.9.0" } }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, "signale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", "dev": true, "requires": { "chalk": "^2.3.2", "figures": "^2.0.0", "pkg-conf": "^2.1.0" }, "dependencies": { "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" } } } }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "spawn-error-forwarder": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", "integrity": "sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk=", "dev": true }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", "dev": true }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", "dev": true, "requires": { "through": "2" } }, "split-on-first": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", "dev": true }, "split2": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", "dev": true, "requires": { "through2": "^2.0.2" }, "dependencies": { "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } } } }, "stream-combiner2": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", "dev": true, "requires": { "duplexer2": "~0.1.0", "readable-stream": "^2.0.2" } }, "strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", "dev": true }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" } }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" } }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { "ansi-regex": "^5.0.0" } }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "requires": { "min-indent": "^1.0.0" } }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" } }, "supports-hyperlinks": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", "dev": true, "requires": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" }, "dependencies": { "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" } } } }, "temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", "dev": true }, "tempy": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", "dev": true, "requires": { "del": "^6.0.0", "is-stream": "^2.0.0", "temp-dir": "^2.0.0", "type-fest": "^0.16.0", "unique-string": "^2.0.0" }, "dependencies": { "type-fest": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", "dev": true } } }, "text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", "dev": true }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, "through2": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", "dev": true, "requires": { "readable-stream": "3" }, "dependencies": { "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } } } }, "to-readable-stream": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz", "integrity": "sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==", "dev": true }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { "is-number": "^7.0.0" } }, "traverse": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", "dev": true }, "trim-newlines": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", "dev": true }, "trim-off-newlines": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", "dev": true }, "type-fest": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz", "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==", "dev": true }, "uglify-js": { "version": "3.13.10", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.10.tgz", "integrity": "sha512-57H3ACYFXeo1IaZ1w02sfA71wI60MGco/IQFjOqK+WtKoprh7Go2/yvd2HPtoJILO2Or84ncLccI4xoHMTSbGg==", "dev": true, "optional": true }, "unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", "dev": true, "requires": { "crypto-random-string": "^2.0.0" } }, "universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", "dev": true }, "universalify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "dev": true }, "url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { "isexe": "^2.0.0" } }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "dependencies": { "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" } }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" } }, "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "yaml": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", "dev": true }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" }, "dependencies": { "yargs-parser": { "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } } } }, "yargs-parser": { "version": "20.2.4", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "dev": true }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true } } } labkit-v1.7.0/package.json000066400000000000000000000016231410103036700154360ustar00rootroot00000000000000{ "name": "labkit", "description": "LabKit is minimalist library to provide functionality for Go services at GitLab.", "repository": { "type": "git", "url": "git+ssh://git@gitlab.com/gitlab-org/labkit.git" }, "author": "GitLab, Inc.", "license": "MIT", "homepage": "https://gitlab.com/gitlab-org/labkit#readme", "devDependencies": { "@commitlint/cli": "^11.0.0", "@commitlint/config-conventional": "^11.0.0", "@semantic-release/gitlab": "^6.2.1", "conventional-changelog-conventionalcommits": "^4.6.0", "semantic-release": "^17.4.4" }, "release": { "plugins": [ [ "@semantic-release/commit-analyzer", { "preset": "conventionalcommits" } ], [ "@semantic-release/release-notes-generator", { "preset": "conventionalcommits" } ], "@semantic-release/gitlab" ] } } labkit-v1.7.0/test.sh000077500000000000000000000006321410103036700144650ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "${SCRIPT_DIR}" for build_tags in \ "" \ "tracer_static tracer_static_jaeger tracer_static_lightstep tracer_static_datadog tracer_static_stackdriver" \ "continuous_profiler_stackdriver" \ ; do ( set -x; go test \ -race \ -tags "${build_tags}" \ ./... ) done labkit-v1.7.0/tidy.sh000077500000000000000000000003711410103036700144570ustar00rootroot00000000000000#!/usr/bin/env bash set -eo pipefail # Check go tidy git diff go.sum go.mod > /tmp/gomod-${CI_JOB_ID}-before go mod tidy git diff go.sum go.mod > /tmp/gomod-${CI_JOB_ID}-after diff -U0 /tmp/gomod-${CI_JOB_ID}-before /tmp/gomod-${CI_JOB_ID}-after labkit-v1.7.0/tracing/000077500000000000000000000000001410103036700145755ustar00rootroot00000000000000labkit-v1.7.0/tracing/connstr/000077500000000000000000000000001410103036700162635ustar00rootroot00000000000000labkit-v1.7.0/tracing/connstr/connection_string_parser.go000066400000000000000000000022561410103036700237200ustar00rootroot00000000000000package connstr import ( "fmt" "net/url" "regexp" ) // Connection strings: // * opentracing://jaeger // * opentracing://datadog // * opentracing://lightstep?access_token=12345 var errInvalidConnection = fmt.Errorf("invalid connection string") // Parse parses a opentracing connection string into a driverName and options map. func Parse(connectionString string) (driverName string, options map[string]string, err error) { if connectionString == "" { return "", nil, errInvalidConnection } URL, err := url.Parse(connectionString) if err != nil { return "", nil, errInvalidConnection } if URL.Scheme != "opentracing" { return "", nil, errInvalidConnection } driverName = URL.Host if driverName == "" { return "", nil, errInvalidConnection } // Connection strings should not have a path if URL.Path != "" { return "", nil, errInvalidConnection } matched, err := regexp.MatchString("^[a-z0-9_]+$", driverName) if err != nil || !matched { return "", nil, errInvalidConnection } query := URL.Query() driverName = URL.Host options = make(map[string]string, len(query)) for k := range query { options[k] = query.Get(k) } return driverName, options, nil } labkit-v1.7.0/tracing/connstr/connection_string_parser_test.go000066400000000000000000000046331410103036700247600ustar00rootroot00000000000000package connstr import ( "reflect" "testing" ) func Test_Parse(t *testing.T) { tests := []struct { name string connectionString string wantDriverName string wantOptions map[string]string wantErr bool }{ { name: "empty", connectionString: "", wantErr: true, }, { name: "invalid", connectionString: "notopentracing://something", wantErr: true, }, { name: "invalidport", connectionString: "opentracing://something:port", wantErr: true, }, { name: "jaeger", connectionString: "opentracing://jaeger", wantDriverName: "jaeger", wantOptions: map[string]string{}, wantErr: false, }, { name: "datadog", connectionString: "opentracing://datadog", wantDriverName: "datadog", wantOptions: map[string]string{}, wantErr: false, }, { name: "lightstep", connectionString: "opentracing://lightstep?api_key=123", wantDriverName: "lightstep", wantOptions: map[string]string{ "api_key": "123", }, wantErr: false, }, { name: "path", connectionString: "opentracing://lightstep/123?api_key=123", wantErr: true, }, { name: "multiple_params", connectionString: "opentracing://lightstep?api_key=123&host=localhost:1234", wantDriverName: "lightstep", wantOptions: map[string]string{ "api_key": "123", "host": "localhost:1234", }, wantErr: false, }, { name: "repeat_params", connectionString: "opentracing://lightstep?api_key=123&host=localhost:1234&host=moo", wantDriverName: "lightstep", wantOptions: map[string]string{ "api_key": "123", "host": "localhost:1234", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotDriverName, getOptions, err := Parse(tt.connectionString) if (err != nil) != tt.wantErr { t.Errorf("ParseConnectionString() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(gotDriverName, tt.wantDriverName) { t.Errorf("ParseConnectionString().driverName = %v, want %v", gotDriverName, tt.wantDriverName) } if !reflect.DeepEqual(getOptions, tt.wantOptions) { t.Errorf("ParseConnectionString().options = %v, want %v", getOptions, tt.wantOptions) } }) } } labkit-v1.7.0/tracing/correlation/000077500000000000000000000000001410103036700171165ustar00rootroot00000000000000labkit-v1.7.0/tracing/correlation/baggage_handler.go000066400000000000000000000016401410103036700225200ustar00rootroot00000000000000package tracingcorrelation import ( "net/http" opentracing "github.com/opentracing/opentracing-go" "gitlab.com/gitlab-org/labkit/correlation" ) // BaggageHandler will set opentracing baggage items with the current correlation_id. func BaggageHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if span := opentracing.SpanFromContext(ctx); span != nil { correlationID := correlation.ExtractFromContext(ctx) if correlationID != "" { span.SetBaggageItem(correlation.FieldName, correlationID) } else { // If the span contains the correlation_id, but the context doesn't // inject it from the span correlationID = span.BaggageItem(correlation.FieldName) if correlationID != "" { ctx = correlation.ContextWithCorrelation(ctx, correlationID) r = r.WithContext(ctx) } } } h.ServeHTTP(w, r) }) } labkit-v1.7.0/tracing/doc.go000066400000000000000000000130701410103036700156720ustar00rootroot00000000000000/* Package tracing is the primary entrypoint into LabKit's distributed tracing functionality. (This documentation assumes some minimal knowledge of Distributed Tracing, and uses tracing terminology without providing definitions. Please review https://opentracing.io/docs/overview/what-is-tracing/ for an broad overview of distributed tracing if you are not familiar with the technology) Internally the `tracing` package relies on Opentracing, but avoids leaking this abstraction. In theory, LabKit could replace Opentracing with another distributed tracing interface, such as Zipkin or OpenCensus, without needing to make changes to the application (other than updating the LabKit dependency, of course). This design decision is deliberate: the package should not leak the underlying tracing implementation. The package provides three primary exports: * `tracing.Initialize()` for initializing the global tracer using the `GITLAB_TRACING` environment variable. * An HTTP Handler middleware, `tracing.Handler()`, for instrumenting incoming HTTP requests. * An HTTP RoundTripper, `tracing.NewRoundTripper()` for instrumenting outbound HTTP requests to other services. The provided example in `example_test.go` demonstrates usage of both the HTTP Middleware and the HTTP RoundTripper. *Initializing the global tracer* Opentracing makes use of a global tracer. Opentracing ships with a default NoOp tracer which does nothing at all. This is always configured, meaning that, without initialization, Opentracing does nothing and has a very low overhead. LabKit's tracing is configured through an environment variable, `GITLAB_TRACING`. This environment variable contains a "connection string"-like configuration, such as: * `opentracing://jaeger?udp_endpoint=localhost:6831` * `opentracing://datadog` * `opentracing://lightstep` * `opentracing://stackdriver?sampler_probability=0.001&project_id=gitlab-pre` The parameters for these connection-strings are implementation specific. This configuration is identical to the one used to configure GitLab's ruby tracing libraries in the `Gitlab::Tracing` package. Having a consistent configuration makes it easy to configure multiple processes at the same time. For example, in GitLab Development Kit, tracing can be configured with a single environment variable, `GITLAB_TRACING=... gdk run`, since `GITLAB_TRACING` will configure Workhorse (written in Go), Gitaly (written in Go) and GitLab's rails components, using the same configuration. *Compiling applications with Tracing support* Go's Opentracing interface does not allow tracing implementations to be loaded dynamically; implementations need to be compiled into the application. With LabKit, this is done conditionally, using build tags. Two build tags need to be specified: * `tracer_static` - this compiles in the static plugin registry support * `tracer_static_[DRIVER_NAME]` - this compile in support for the given driver. For example, to compile support for Jaeger, compile your Go app with `tracer_static,tracer_static_jaeger` Note that multiple (or all) drivers can be compiled in alongside one another: using the tags: `tracer_static,tracer_static_jaeger,tracer_static_lightstep,tracer_static_datadog,tracer_static_stackdriver` If the `GITLAB_TRACING` environment variable references an unknown or unregistered driver, it will log a message and continue without tracing. This is a deliberate decision: the risk of bringing down a cluster during a rollout with a misconfigured tracer configuration is greater than the risk of an operator loosing some time because their application was not compiled with the correct tracers. *Using the HTTP Handler middleware to instrument incoming HTTP requests* When an incoming HTTP request arrives on the server, it may already include Distributed Tracing headers, propagated from an upstream service. The tracing middleware will attempt to extract the tracing information from the headers (the exact headers used are tracing implementation specific), set up a span and pass the information through the request context. It is up to the Opentracing implementation to decide whether the span will be sent to the tracing infrastructure. This will be implementation-specific, but generally relies on server load, sampler configuration, whether an error occurred, whether certain spans took an anomalous amount of time, etc. *Using the HTTP RoundTripper to instrument outgoing HTTP requests* The RoundTripper should be added to the HTTP client RoundTripper stack (see the example). When an outbound HTTP request is sent from the HTTP client, the RoundTripper will determine whether there is an active span and if so, will inject headers into the outgoing HTTP request identifying the span. The details of these headers is implementation specific. It is important to ensure that the context is passed into the outgoing request, using `req.WithContext(ctx)` so that the correct span information can be injected into the request headers. *Propagating tracing information to child processes* Sometimes we want a trace to continue from a parent process to a spawned child process. For this, the tracing package provides `tracing.NewEnvInjector()` and `tracing.ExtractFromEnv()`, for the parent and child processes respectively. NewEnvInjector() will configure a []string array of environment variables, ensuring they have the correct tracing configuration and any trace and span identifiers. NewEnvInjector() should be called in the child process and will extract the trace and span information from the environment. Please review the examples in the godocs for details of how to implement both approaches. */ package tracing labkit-v1.7.0/tracing/env_extractor.go000066400000000000000000000034511410103036700200120ustar00rootroot00000000000000package tracing import ( "context" "os" "strings" opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "gitlab.com/gitlab-org/labkit/correlation" ) // ExtractFromEnv will extract a span from the environment after it has been passed in // from the parent process. Returns a new context, and a defer'able function, which // should be called on process termination. func ExtractFromEnv(ctx context.Context, opts ...ExtractFromEnvOption) (context.Context, func()) { /* config not yet used */ applyExtractFromEnvOptions(opts) tracer := opentracing.GlobalTracer() // Extract the Correlation-ID envMap := environAsMap(os.Environ()) correlationID := envMap[envCorrelationIDKey] if correlationID != "" { ctx = correlation.ContextWithCorrelation(ctx, correlationID) } // Attempt to deserialize tracing identifiers wireContext, err := tracer.Extract( opentracing.TextMap, opentracing.TextMapCarrier(envMap)) if err != nil { /* Clients could send bad data, in which case we simply ignore it */ return ctx, func() {} } // Create the span referring to the RPC client if available. // If wireContext == nil, a root span will be created. additionalStartSpanOpts := []opentracing.StartSpanOption{ ext.RPCServerOption(wireContext), } if correlationID != "" { additionalStartSpanOpts = append(additionalStartSpanOpts, opentracing.Tag{Key: "correlation_id", Value: correlationID}) } serverSpan := opentracing.StartSpan( "execute", additionalStartSpanOpts..., ) ctx = opentracing.ContextWithSpan(ctx, serverSpan) return ctx, func() { serverSpan.Finish() } } func environAsMap(env []string) map[string]string { envMap := make(map[string]string, len(env)) for _, v := range env { s := strings.SplitN(v, "=", 2) envMap[s[0]] = s[1] } return envMap } labkit-v1.7.0/tracing/env_extractor_option.go000066400000000000000000000005361410103036700214030ustar00rootroot00000000000000package tracing type extractFromEnvConfig struct{} // ExtractFromEnvOption will configure an environment injector. type ExtractFromEnvOption func(*extractFromEnvConfig) func applyExtractFromEnvOptions(opts []ExtractFromEnvOption) extractFromEnvConfig { config := extractFromEnvConfig{} for _, v := range opts { v(&config) } return config } labkit-v1.7.0/tracing/env_extractor_test.go000066400000000000000000000041611410103036700210500ustar00rootroot00000000000000package tracing import ( "context" "os" "reflect" "testing" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/labkit/correlation" ) func TestExtractFromEnv(t *testing.T) { tests := []struct { name string ctx context.Context opts []ExtractFromEnvOption additionalEnv map[string]string wantCorrelationID string }{ { name: "no_options", ctx: context.Background(), opts: []ExtractFromEnvOption{}, }, { name: "pass_correlation_id", ctx: context.Background(), opts: []ExtractFromEnvOption{}, additionalEnv: map[string]string{ envCorrelationIDKey: "abc123", }, wantCorrelationID: "abc123", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resetEnvironment := addAdditionalEnv(tt.additionalEnv) defer resetEnvironment() ctx, finished := ExtractFromEnv(tt.ctx, tt.opts...) require.NotNil(t, ctx, "ctx is nil") require.NotNil(t, finished, "finished is nil") gotCorrelationID := correlation.ExtractFromContext(ctx) require.Equal(t, tt.wantCorrelationID, gotCorrelationID) defer finished() }) } } func Test_environAsMap(t *testing.T) { tests := []struct { name string env []string want map[string]string }{ { name: "trivial", env: nil, want: map[string]string{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := environAsMap(tt.env); !reflect.DeepEqual(got, tt.want) { t.Errorf("environAsMap() = %v, want %v", got, tt.want) } }) } } // addAdditionalEnv will configure additional environment values // and return a deferrable function to reset the environment to // it's original state after the test. func addAdditionalEnv(envMap map[string]string) func() { prevValues := map[string]string{} unsetValues := []string{} for k, v := range envMap { value, exists := os.LookupEnv(k) if exists { prevValues[k] = value } else { unsetValues = append(unsetValues, k) } os.Setenv(k, v) } return func() { for k, v := range prevValues { os.Setenv(k, v) } for _, k := range unsetValues { os.Unsetenv(k) } } } labkit-v1.7.0/tracing/env_injector.go000066400000000000000000000042331410103036700176130ustar00rootroot00000000000000package tracing import ( "context" "fmt" "os" "sort" opentracing "github.com/opentracing/opentracing-go" "gitlab.com/gitlab-org/labkit/correlation" logkit "gitlab.com/gitlab-org/labkit/log" ) // envCorrelationIDKey is used to pass the current correlation-id over to the child process. const envCorrelationIDKey = "CORRELATION_ID" // EnvInjector will inject tracing information into an environment in preparation for // spawning a child process. This includes trace and span identifiers, as well // as the GITLAB_TRACING configuration. Will gracefully degrade if tracing is // not configured, or an active span is not currently available. type EnvInjector func(ctx context.Context, env []string) []string // NewEnvInjector will create a new environment injector. func NewEnvInjector(opts ...EnvInjectorOption) EnvInjector { /* config not yet used */ applyEnvInjectorOptions(opts) return func(ctx context.Context, env []string) []string { envMap := map[string]string{} // Pass the Correlation-ID through the environment if set correlationID := correlation.ExtractFromContext(ctx) if correlationID != "" { envMap[envCorrelationIDKey] = correlationID } // Also include the GITLAB_TRACING configuration so that // the child process knows how to configure itself v, ok := os.LookupEnv(tracingEnvKey) if ok { envMap[tracingEnvKey] = v } span := opentracing.SpanFromContext(ctx) if span == nil { // If no active span, short circuit return appendMapToEnv(env, envMap) } carrier := opentracing.TextMapCarrier(envMap) err := span.Tracer().Inject(span.Context(), opentracing.TextMap, carrier) if err != nil { logkit.ContextLogger(ctx).WithError(err).Error("tracing span injection failed") } return appendMapToEnv(env, envMap) } } // appendMapToEnv takes a map of key,value pairs and appends it to an // array of environment variable pairs in `K=V` string pairs. func appendMapToEnv(env []string, envMap map[string]string) []string { additionalItems := []string{} for k, v := range envMap { additionalItems = append(additionalItems, fmt.Sprintf("%s=%s", k, v)) } sort.Strings(additionalItems) return append(env, additionalItems...) } labkit-v1.7.0/tracing/env_injector_option.go000066400000000000000000000005061410103036700212020ustar00rootroot00000000000000package tracing type envInjectorConfig struct{} // EnvInjectorOption will configure an environment injector. type EnvInjectorOption func(*envInjectorConfig) func applyEnvInjectorOptions(opts []EnvInjectorOption) envInjectorConfig { config := envInjectorConfig{} for _, v := range opts { v(&config) } return config } labkit-v1.7.0/tracing/env_injector_test.go000066400000000000000000000065131410103036700206550ustar00rootroot00000000000000package tracing import ( "context" "os" "reflect" "testing" "github.com/stretchr/testify/require" "gitlab.com/gitlab-org/labkit/correlation" ) func TestNewEnvInjector(t *testing.T) { tests := []struct { name string tracingEnv string correlationID string env []string wantEnv []string }{ { name: "trivial", }, { name: "gitlab_tracing_config", tracingEnv: "opentracing://null", wantEnv: []string{ "GITLAB_TRACING=opentracing://null", }, }, { name: "merge", tracingEnv: "opentracing://null", env: []string{ "A=1", }, wantEnv: []string{ "A=1", "GITLAB_TRACING=opentracing://null", }, }, { name: "correlation", correlationID: "abc123", env: []string{}, wantEnv: []string{ "CORRELATION_ID=abc123", }, }, { name: "correlation_merge", tracingEnv: "opentracing://null", correlationID: "abc123", env: []string{"A=1"}, wantEnv: []string{ "A=1", "CORRELATION_ID=abc123", "GITLAB_TRACING=opentracing://null", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.tracingEnv != "" { resetEnvironment := modifyEnvironment(tracingEnvKey, tt.tracingEnv) defer resetEnvironment() } else { resetEnvironment := clearEnvironment(tracingEnvKey) defer resetEnvironment() } envInjector := NewEnvInjector() ctx := context.Background() if tt.correlationID != "" { ctx = correlation.ContextWithCorrelation(ctx, tt.correlationID) } got := envInjector(ctx, tt.env) require.Equal(t, tt.wantEnv, got) }) } } // modifyEnvironment will change an environment variable and return a func suitable // for `defer` to change the value back. func modifyEnvironment(key string, value string) func() { oldValue, hasOldValue := os.LookupEnv(key) os.Setenv(key, value) return func() { if hasOldValue { os.Setenv(key, oldValue) } else { os.Unsetenv(key) } } } // clearEnvironment will clear an environment variable and return a func suitable // for `defer` to change the value back. func clearEnvironment(key string) func() { oldValue, hasOldValue := os.LookupEnv(key) if !hasOldValue { return func() { // Nothing to do } } os.Unsetenv(key) return func() { if hasOldValue { os.Setenv(key, oldValue) } } } func Test_appendMapToEnv(t *testing.T) { tests := []struct { name string env []string envMap map[string]string want []string }{ { name: "empty_case", env: nil, envMap: nil, want: nil, }, { name: "no_new_values", env: []string{"A=1"}, envMap: nil, want: []string{"A=1"}, }, { name: "no_original_values", env: nil, envMap: map[string]string{"A": "1"}, want: []string{"A=1"}, }, { name: "merge_values", env: []string{"A=1"}, envMap: map[string]string{"B": "2"}, want: []string{"A=1", "B=2"}, }, { name: "merge_conflicts", env: []string{"A=1", "C=3"}, envMap: map[string]string{"B": "2", "C": "4"}, want: []string{"A=1", "C=3", "B=2", "C=4"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := appendMapToEnv(tt.env, tt.envMap); !reflect.DeepEqual(got, tt.want) { t.Errorf("appendMapToEnv() = %v, want %v", got, tt.want) } }) } } labkit-v1.7.0/tracing/errors.go000066400000000000000000000002701410103036700164370ustar00rootroot00000000000000package tracing import ( "fmt" ) // ErrConfiguration is returned when the tracer is not properly configured. var ErrConfiguration = fmt.Errorf("tracing is not properly configured") labkit-v1.7.0/tracing/examples_test.go000066400000000000000000000043461410103036700200100ustar00rootroot00000000000000package tracing_test import ( "context" "fmt" "io" "net/http" "os/exec" "time" log "github.com/sirupsen/logrus" "gitlab.com/gitlab-org/labkit/tracing" ) // This example shows how to initialize tracing // and then wrap all incoming calls. func Example() { // Tell the tracer to initialize as service "gitlab-wombat" tracing.Initialize(tracing.WithServiceName("gitlab-wombat")) tr := &http.Transport{ MaxIdleConns: 10, IdleConnTimeout: 30 * time.Second, DisableCompression: true, } client := &http.Client{ Transport: tracing.NewRoundTripper(tr), } // Listen and propagate traces http.Handle("/foo", // Add the tracing middleware in tracing.Handler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { req, err := http.NewRequest(http.MethodGet, "http://localhost:8080/bar", nil) if err != nil { w.WriteHeader(500) return } req = req.WithContext(r.Context()) resp, err := client.Do(req) if err != nil { w.WriteHeader(500) return } defer resp.Body.Close() _, err = io.Copy(w, resp.Body) if err != nil { w.WriteHeader(500) return } }), // Use this route identifier with the tracing middleware tracing.WithRouteIdentifier("/foo"), )) // Listen and propagate traces http.Handle("/bar", // Add the tracing middleware in tracing.Handler( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "bar") }), // Use this route identifier with the tracing middleware tracing.WithRouteIdentifier("/bar"), )) log.Fatal(http.ListenAndServe(":0", nil)) } func ExampleNewEnvInjector() { envInjector := tracing.NewEnvInjector() cmd := exec.Command("ls") env := []string{ "FOO=bar", } // envInjector will inject any required values cmd.Env = envInjector(context.Background(), env) if err := cmd.Run(); err != nil { log.WithError(err).Fatal("Command failed") } } func ExampleExtractFromEnv() { // Tell the tracer to initialize as service "gitlab-child-process" tracing.Initialize(tracing.WithServiceName("gitlab-child-process")) ctx, finished := tracing.ExtractFromEnv(context.Background()) defer finished() // Program execution happens here... func(_ context.Context) {}(ctx) } labkit-v1.7.0/tracing/grpc/000077500000000000000000000000001410103036700155305ustar00rootroot00000000000000labkit-v1.7.0/tracing/grpc/client_interceptors.go000066400000000000000000000011431410103036700221350ustar00rootroot00000000000000package grpccorrelation import ( grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" "google.golang.org/grpc" ) // UnaryClientTracingInterceptor will create a unary client tracing interceptor. func UnaryClientTracingInterceptor() grpc.UnaryClientInterceptor { return grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithFilterFunc(healthCheckFilterFunc)) } // StreamClientTracingInterceptor will create a streaming client tracing interceptor. func StreamClientTracingInterceptor() grpc.StreamClientInterceptor { return grpc_opentracing.StreamClientInterceptor() } labkit-v1.7.0/tracing/grpc/healthcheck_filter.go000066400000000000000000000010431410103036700216650ustar00rootroot00000000000000package grpccorrelation import "context" // grpcHealthCheckMethodFullName is the name of the standard GRPC health check full method name. const grpcHealthCheckMethodFullName = "/grpc.health.v1.Health/Check" // healthCheckFilterFunc will exclude all GRPC health checks from tracing // since these calls are high frequency, but low value from a tracing point // of view, excluding them reduces the tracing load. func healthCheckFilterFunc(_ context.Context, fullMethodName string) bool { return fullMethodName != grpcHealthCheckMethodFullName } labkit-v1.7.0/tracing/grpc/healthcheck_filter_test.go000066400000000000000000000013511410103036700227260ustar00rootroot00000000000000package grpccorrelation import ( "context" "testing" "github.com/stretchr/testify/require" ) func Test_healthCheckFilterFunc(t *testing.T) { tests := []struct { name string fullMethodName string want bool }{ { name: "empty", fullMethodName: "", want: true, }, { name: "normal", fullMethodName: "/gitaly.SSHService/SSHUploadPack", want: true, }, { name: "normal", fullMethodName: grpcHealthCheckMethodFullName, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := healthCheckFilterFunc(context.Background(), tt.fullMethodName) require.Equal(t, tt.want, got) }) } } labkit-v1.7.0/tracing/grpc/server_interceptors.go000066400000000000000000000011431410103036700221650ustar00rootroot00000000000000package grpccorrelation import ( grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" "google.golang.org/grpc" ) // UnaryServerTracingInterceptor will create a unary server tracing interceptor. func UnaryServerTracingInterceptor() grpc.UnaryServerInterceptor { return grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithFilterFunc(healthCheckFilterFunc)) } // StreamServerTracingInterceptor will create a streaming server tracing interceptor. func StreamServerTracingInterceptor() grpc.StreamServerInterceptor { return grpc_opentracing.StreamServerInterceptor() } labkit-v1.7.0/tracing/impl/000077500000000000000000000000001410103036700155365ustar00rootroot00000000000000labkit-v1.7.0/tracing/impl/constants.go000066400000000000000000000001021410103036700200720ustar00rootroot00000000000000package impl const keyStrictConnectionParsing = "strict_parsing" labkit-v1.7.0/tracing/impl/datadog_tracer.go000066400000000000000000000011201410103036700210220ustar00rootroot00000000000000// +build tracer_static,tracer_static_datadog package impl import ( "io" opentracing "github.com/opentracing/opentracing-go" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentracer" "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" ) func tracerFactory(config map[string]string) (opentracing.Tracer, io.Closer, error) { opts := []tracer.StartOption{} if config["service_name"] != "" { opts = append(opts, tracer.WithServiceName(config["service_name"])) } return opentracer.New(opts...), nil, nil } func init() { // nolint:gochecknoinits registerTracer("datadog", tracerFactory) } labkit-v1.7.0/tracing/impl/errors.go000066400000000000000000000002631410103036700174020ustar00rootroot00000000000000package impl import "errors" // ErrConfiguration is the base exception for tracing configuration errors. var ErrConfiguration = errors.New("jaeger tracer: configuration error") labkit-v1.7.0/tracing/impl/jaeger_tracer.go000066400000000000000000000053761410103036700206750ustar00rootroot00000000000000// +build tracer_static,tracer_static_jaeger package impl import ( "fmt" "io" "strconv" opentracing "github.com/opentracing/opentracing-go" log "github.com/sirupsen/logrus" jaegercfg "github.com/uber/jaeger-client-go/config" jaegerlog "github.com/uber/jaeger-client-go/log" ) type traceConfigMapper func(traceCfg *jaegercfg.Configuration, value string) ([]jaegercfg.Option, error) var configMapper = map[string]traceConfigMapper{ "service_name": func(traceCfg *jaegercfg.Configuration, value string) ([]jaegercfg.Option, error) { traceCfg.ServiceName = value return nil, nil }, "debug": func(traceCfg *jaegercfg.Configuration, value string) ([]jaegercfg.Option, error) { return []jaegercfg.Option{jaegercfg.Logger(jaegerlog.StdLogger)}, nil }, "sampler": func(traceCfg *jaegercfg.Configuration, value string) ([]jaegercfg.Option, error) { if traceCfg.Sampler == nil { traceCfg.Sampler = &jaegercfg.SamplerConfig{} } traceCfg.Sampler.Type = value return nil, nil }, "sampler_param": func(traceCfg *jaegercfg.Configuration, value string) ([]jaegercfg.Option, error) { if traceCfg.Sampler == nil { traceCfg.Sampler = &jaegercfg.SamplerConfig{} } valuef, err := strconv.ParseFloat(value, 64) if err != nil { return nil, fmt.Errorf("sampler_param must be a float: %w", ErrConfiguration) } traceCfg.Sampler.Param = valuef return nil, nil }, "http_endpoint": func(traceCfg *jaegercfg.Configuration, value string) ([]jaegercfg.Option, error) { if traceCfg.Reporter == nil { traceCfg.Reporter = &jaegercfg.ReporterConfig{} } traceCfg.Reporter.CollectorEndpoint = value return nil, nil }, "udp_endpoint": func(traceCfg *jaegercfg.Configuration, value string) ([]jaegercfg.Option, error) { if traceCfg.Reporter == nil { traceCfg.Reporter = &jaegercfg.ReporterConfig{} } traceCfg.Reporter.LocalAgentHostPort = value return nil, nil }, } func jaegerTracerFactory(config map[string]string) (opentracing.Tracer, io.Closer, error) { traceCfg, err := jaegercfg.FromEnv() if err != nil { return nil, nil, err } options := []jaegercfg.Option{} // Convert the configuration map into a jaeger configuration for k, v := range config { mapper := configMapper[k] if k == keyStrictConnectionParsing { continue } if mapper != nil { o, err := mapper(traceCfg, v) if err != nil { return nil, nil, err } options = append(options, o...) } else { if config[keyStrictConnectionParsing] != "" { return nil, nil, fmt.Errorf("jaeger tracer: invalid option: %s: %w", k, ErrConfiguration) } log.Printf("jaeger tracer: warning: ignoring unknown configuration option: %s", k) } } return traceCfg.NewTracer(options...) } func init() { // nolint:gochecknoinits registerTracer("jaeger", jaegerTracerFactory) } labkit-v1.7.0/tracing/impl/jaeger_tracer_test.go000066400000000000000000000043101410103036700217170ustar00rootroot00000000000000// +build tracer_static,tracer_static_jaeger package impl import ( "testing" "gitlab.com/gitlab-org/labkit/tracing/connstr" ) func TestTracerFactory(t *testing.T) { tests := []struct { connectionString string wantErr bool strict bool }{ { connectionString: "opentracing://jaeger", wantErr: false, strict: true, }, { connectionString: "opentracing://jaeger?debug=true", wantErr: false, strict: true, }, { connectionString: "opentracing://jaeger?sampler=const&sampler_param=0", wantErr: false, strict: true, }, { connectionString: "opentracing://jaeger?sampler=probabilistic&sampler_param=0.1", wantErr: false, strict: true, }, { connectionString: "opentracing://jaeger?http_endpoint=http%3A%2F%2Flocalhost%3A14268%2Fapi%2Ftraces", wantErr: false, strict: true, }, { connectionString: "opentracing://jaeger?udp_endpoint=10.0.0.1:1234", wantErr: false, strict: true, }, { connectionString: "opentracing://jaeger?service_name=api", wantErr: false, strict: true, }, { connectionString: "opentracing://jaeger?invalid_option=blah&relaxed", wantErr: false, strict: false, }, { connectionString: "opentracing://jaeger?invalid_option=blah&strict", wantErr: true, strict: true, }, } for _, tt := range tests { t.Run(tt.connectionString, func(t *testing.T) { _, options, err := connstr.Parse(tt.connectionString) if err != nil { t.Errorf("TracerFactory() error = unable to parse connection string: %v", err) } if tt.strict { options[keyStrictConnectionParsing] = "1" } options["service_name"] = "test" gotTracer, gotCloser, err := jaegerTracerFactory(options) if (err != nil) != tt.wantErr { t.Errorf("TracerFactory() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { if gotTracer == nil { t.Errorf("TracerFactory() expected a tracer, got nil") } if gotCloser == nil { t.Errorf("TracerFactory() expected a closed, got nil") } } }) } } labkit-v1.7.0/tracing/impl/lightstep_tracer.go000066400000000000000000000036211410103036700214320ustar00rootroot00000000000000// +build tracer_static,tracer_static_lightstep package impl import ( "context" "fmt" "io" lightstep "github.com/lightstep/lightstep-tracer-go" opentracing "github.com/opentracing/opentracing-go" log "github.com/sirupsen/logrus" ) type lightstepCloser struct { tracer lightstep.Tracer } func (c *lightstepCloser) Close() error { lightstep.Close(context.Background(), c.tracer) return nil } var lightstepConfigMapper = map[string]func(traceCfg *lightstep.Options, value string) error{ "service_name": func(options *lightstep.Options, value string) error { options.Tags[lightstep.ComponentNameKey] = value return nil }, "access_token": func(options *lightstep.Options, value string) error { options.AccessToken = value return nil }, } func lightstepTracerFactory(config map[string]string) (opentracing.Tracer, io.Closer, error) { options := lightstep.Options{ Tags: map[string]interface{}{}, } // Convert the configuration map into a jaeger configuration for k, v := range config { mapper := lightstepConfigMapper[k] if k == keyStrictConnectionParsing { continue } if mapper != nil { err := mapper(&options, v) if err != nil { return nil, nil, err } } else { if config[keyStrictConnectionParsing] != "" { return nil, nil, fmt.Errorf("lightstep tracer: invalid option: %s: %w", k, ErrConfiguration) } log.Printf("lightstep tracer: warning: ignoring unknown configuration option: %s", k) } } if options.AccessToken == "" { return nil, nil, fmt.Errorf("failed to parse access_token from config: %q: %w", config, ErrConfiguration) } tracer := lightstep.NewTracer(options) if tracer == nil { return nil, nil, fmt.Errorf("lightstep tracer: unable to create tracer, review log messages: %w", ErrConfiguration) } return tracer, &lightstepCloser{tracer}, nil } func init() { // nolint:gochecknoinits registerTracer("lightstep", lightstepTracerFactory) } labkit-v1.7.0/tracing/impl/lightstep_tracer_test.go000066400000000000000000000030321410103036700224650ustar00rootroot00000000000000// +build tracer_static,tracer_static_lightstep package impl import ( "testing" "gitlab.com/gitlab-org/labkit/tracing/connstr" ) func Test_lightstepTracerFactory(t *testing.T) { tests := []struct { connectionString string wantErr bool strict bool }{ { connectionString: "opentracing://lightstep", wantErr: true, strict: true, }, { connectionString: "opentracing://lightstep?access_token=12345", wantErr: false, strict: true, }, { connectionString: "opentracing://lightstep?access_token=12345&relaxed", wantErr: false, strict: false, }, { connectionString: "opentracing://lightstep?access_token=12345&strict", wantErr: true, strict: true, }, } for _, tt := range tests { t.Run(tt.connectionString, func(t *testing.T) { _, options, err := connstr.Parse(tt.connectionString) if err != nil { t.Errorf("TracerFactory() error = unable to parse connection string: %v", err) } if tt.strict { options[keyStrictConnectionParsing] = "1" } options["service_name"] = "test" gotTracer, gotCloser, err := lightstepTracerFactory(options) if (err != nil) != tt.wantErr { t.Errorf("TracerFactory() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { if gotTracer == nil { t.Errorf("TracerFactory() expected a tracer, got nil") } if gotCloser == nil { t.Errorf("TracerFactory() expected a closed, got nil") } } }) } } labkit-v1.7.0/tracing/impl/null_tracer.go000066400000000000000000000006431410103036700204020ustar00rootroot00000000000000// +build !tracer_static package impl import ( "fmt" "io" opentracing "github.com/opentracing/opentracing-go" ) // New will instantiate a new instance of the tracer, given the driver and configuration func New(driverName string, config map[string]string) (opentracing.Tracer, io.Closer, error) { return nil, nil, fmt.Errorf("tracer: binary compiled without tracer support: cannot load driver %s", driverName) } labkit-v1.7.0/tracing/impl/stackdriver_tracer.go000066400000000000000000000236001410103036700217470ustar00rootroot00000000000000// +build tracer_static,tracer_static_stackdriver package impl import ( "context" "encoding/base64" "fmt" "io" "reflect" "strconv" "time" "contrib.go.opencensus.io/exporter/stackdriver" opentracing "github.com/opentracing/opentracing-go" opentracinglog "github.com/opentracing/opentracing-go/log" log "github.com/sirupsen/logrus" "go.opencensus.io/trace" "go.opencensus.io/trace/propagation" ) // to play nice with grpc_opentracing tagsCarrier // https://github.com/grpc-ecosystem/go-grpc-middleware/blob/a77ba4df9c270ec918ed6a6d506309078e3e4c4d/tracing/opentracing/id_extract.go#L30 // https://github.com/grpc-ecosystem/go-grpc-middleware/blob/a77ba4df9c270ec918ed6a6d506309078e3e4c4d/tracing/opentracing/options.go#L48 var traceHeader = "uber-trace-id" // https://pkg.go.dev/github.com/opentracing/opentracing-go#Tracer type adapterTracer struct { exporter *stackdriver.Exporter } // opencensus does not support overwriting StartTime; so we only implement Tags // https://pkg.go.dev/github.com/opentracing/opentracing-go#StartSpanOption func (tracer *adapterTracer) StartSpan(operationName string, opts ...opentracing.StartSpanOption) opentracing.Span { sso := opentracing.StartSpanOptions{} for _, o := range opts { o.Apply(&sso) } var ctx context.Context var adapterSpanCtx *adapterSpanContext ctx = context.Background() for _, ref := range sso.References { if ref.Type == opentracing.ChildOfRef { if v, ok := ref.ReferencedContext.(adapterSpanContext); ok { ctx = v.ctx adapterSpanCtx = &v break } } } var span *trace.Span if adapterSpanCtx != nil && adapterSpanCtx.remote { ctx, span = trace.StartSpanWithRemoteParent(ctx, operationName, *adapterSpanCtx.ocSpanCtx) } else { ctx, span = trace.StartSpan(ctx, operationName) } adapterSpan := &adapterSpan{span, tracer, adapterSpanContext{ctx, false, nil}} for k, v := range sso.Tags { adapterSpan.SetTag(k, v) } return adapterSpan } func (tracer *adapterTracer) Inject(sm opentracing.SpanContext, format interface{}, carrier interface{}) error { c, ok := sm.(adapterSpanContext) if !ok { return opentracing.ErrInvalidSpanContext } if format != opentracing.TextMap && format != opentracing.HTTPHeaders { return opentracing.ErrUnsupportedFormat } ocSpanCtx := trace.FromContext(c.ctx).SpanContext() encoded := base64.StdEncoding.EncodeToString(propagation.Binary(ocSpanCtx)) carrier.(opentracing.TextMapWriter).Set(traceHeader, string(encoded)) return nil } func (tracer *adapterTracer) Extract(format interface{}, carrier interface{}) (opentracing.SpanContext, error) { if format != opentracing.TextMap && format != opentracing.HTTPHeaders { return nil, opentracing.ErrUnsupportedFormat } var ocSpanCtx trace.SpanContext err := carrier.(opentracing.TextMapReader).ForeachKey(func(key, val string) error { var ok bool if key == traceHeader { decoded, err := base64.StdEncoding.DecodeString(val) if err != nil { return err } ocSpanCtx, ok = propagation.FromBinary(decoded) if !ok { return opentracing.ErrInvalidCarrier } } return nil }) if err != nil { return nil, err } return adapterSpanContext{ ctx: context.Background(), remote: true, ocSpanCtx: &ocSpanCtx, }, nil } type adapterSpanContext struct { ctx context.Context remote bool ocSpanCtx *trace.SpanContext } func (c adapterSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {} // https://pkg.go.dev/go.opencensus.io/trace#Span // https://pkg.go.dev/github.com/opentracing/opentracing-go#Span type adapterSpan struct { span trace.SpanInterface tracer opentracing.Tracer spanContext adapterSpanContext } func (span *adapterSpan) Finish() { span.span.End() } func (span *adapterSpan) FinishWithOptions(opts opentracing.FinishOptions) { span.span.End() } func (span *adapterSpan) Context() opentracing.SpanContext { return span.spanContext } func (span *adapterSpan) SetOperationName(operationName string) opentracing.Span { span.span.SetName(operationName) return span } func castToAttribute(key string, value interface{}) []trace.Attribute { switch v := reflect.ValueOf(value); v.Kind() { case reflect.Bool: return []trace.Attribute{trace.BoolAttribute(key, v.Bool())} case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return []trace.Attribute{trace.Int64Attribute(key, v.Int())} case reflect.Float32, reflect.Float64: return []trace.Attribute{trace.Float64Attribute(key, v.Float())} case reflect.String: chunks := chunkString(v.String(), 256) attrs := []trace.Attribute{} for i, chunk := range chunks { var k string if i == 0 { k = key } else { k = key + "." + strconv.Itoa(i) } attrs = append(attrs, trace.StringAttribute(k, chunk)) } return attrs default: return []trace.Attribute{trace.StringAttribute(key, fmt.Sprintf("castToAttribute not implemented for type %+v", v.Kind()))} } } // stackdriver limits attribute values to 256 bytes // we use runes here, hopefully that's close enough // values get truncated silently, so we can set $key to the full value // and then $key.1, $key.2, $key.3 are explicitly sliced. func chunkString(s string, chunkSize int) []string { if len(s) == 0 { return []string{s} } var strs []string for i := 0; i*chunkSize < len(s); i++ { end := (i + 1) * chunkSize if end > len(s) { end = len(s) } strs = append(strs, s[i*chunkSize:end]) } return strs } func (span *adapterSpan) SetTag(key string, value interface{}) opentracing.Span { span.span.AddAttributes(castToAttribute(key, value)...) return span } func (span *adapterSpan) LogFields(fields ...opentracinglog.Field) { eventName := "" attributes := []trace.Attribute{} for _, field := range fields { if field.Key() == "event" { eventName = field.String() continue } attributes = append(attributes, castToAttribute(field.Key(), field.Value())...) } span.span.Annotate(attributes, eventName) } func (span *adapterSpan) LogKV(alternatingKeyValues ...interface{}) { if (len(alternatingKeyValues) % 2) != 0 { log.Print("stackdriver tracer: warning: even number of arguments required to LogKV") } attributes := []trace.Attribute{} eventName := "" for i := 0; i < len(alternatingKeyValues); i += 2 { key := alternatingKeyValues[i].(string) value := alternatingKeyValues[i+1] if key == "event" { eventName = value.(string) continue } attributes = append(attributes, castToAttribute(key, value)...) } span.span.Annotate(attributes, eventName) } func (span *adapterSpan) SetBaggageItem(restrictedKey, value string) opentracing.Span { return span } func (span *adapterSpan) BaggageItem(restrictedKey string) string { // not implemented return "" } func (span *adapterSpan) Tracer() opentracing.Tracer { return span.tracer } // Deprecated: use LogFields or LogKV. func (span *adapterSpan) LogEvent(event string) { // not implemented } // Deprecated: use LogFields or LogKV. func (span *adapterSpan) LogEventWithPayload(event string, payload interface{}) { // not implemented } // Deprecated: use LogFields or LogKV. func (span *adapterSpan) Log(data opentracing.LogData) { // not implemented } type stackdriverCloser struct { exporter *stackdriver.Exporter } func (c stackdriverCloser) Close() error { c.exporter.Flush() return nil } type stackdriverConfig struct { options *stackdriver.Options sampler trace.Sampler } // https://pkg.go.dev/contrib.go.opencensus.io/exporter/stackdriver#Options var stackdriverConfigMapper = map[string]func(traceCfg *stackdriverConfig, value string) error{ "project_id": func(stackdriverCfg *stackdriverConfig, value string) error { stackdriverCfg.options.ProjectID = value return nil }, "location": func(stackdriverCfg *stackdriverConfig, value string) error { stackdriverCfg.options.Location = value return nil }, "bundle_delay_threshold": func(stackdriverCfg *stackdriverConfig, value string) error { d, err := time.ParseDuration(value) if err != nil { return err } stackdriverCfg.options.BundleDelayThreshold = d return nil }, "bundle_count_threshold": func(stackdriverCfg *stackdriverConfig, value string) error { v, err := strconv.Atoi(value) if err != nil { return err } stackdriverCfg.options.BundleCountThreshold = v return nil }, "trace_spans_buffer_max_bytes": func(stackdriverCfg *stackdriverConfig, value string) error { v, err := strconv.Atoi(value) if err != nil { return err } stackdriverCfg.options.TraceSpansBufferMaxBytes = v return nil }, "timeout": func(stackdriverCfg *stackdriverConfig, value string) error { d, err := time.ParseDuration(value) if err != nil { return err } stackdriverCfg.options.Timeout = d return nil }, "number_of_workers": func(stackdriverCfg *stackdriverConfig, value string) error { v, err := strconv.Atoi(value) if err != nil { return err } stackdriverCfg.options.NumberOfWorkers = v return nil }, "sampler_probability": func(stackdriverCfg *stackdriverConfig, value string) error { v, err := strconv.ParseFloat(value, 64) if err != nil { return err } stackdriverCfg.sampler = trace.ProbabilitySampler(v) return nil }, } func stackdriverTracerFactory(config map[string]string) (opentracing.Tracer, io.Closer, error) { stackdriverCfg := &stackdriverConfig{ options: &stackdriver.Options{}, sampler: trace.NeverSample(), } for k, v := range config { mapper := stackdriverConfigMapper[k] if mapper != nil { err := mapper(stackdriverCfg, v) if err != nil { return nil, nil, err } } else { log.Printf("stackdriver tracer: warning: ignoring unknown configuration option: %s", k) } } exporter, err := stackdriver.NewExporter(*stackdriverCfg.options) if err != nil { return nil, nil, err } trace.RegisterExporter(exporter) trace.ApplyConfig(trace.Config{DefaultSampler: stackdriverCfg.sampler}) return &adapterTracer{exporter}, &stackdriverCloser{exporter}, nil } func init() { registerTracer("stackdriver", stackdriverTracerFactory) } labkit-v1.7.0/tracing/impl/stackdriver_tracer_test.go000066400000000000000000000057511410103036700230150ustar00rootroot00000000000000// +build tracer_static,tracer_static_jaeger package impl import ( "reflect" "strings" "testing" "go.opencensus.io/trace" ) type unsupportedType struct{} func TestOcSpanAdapterCastToAttribute(t *testing.T) { tests := []struct { name string key string value interface{} want []trace.Attribute }{ { name: "true", key: "foo", value: true, want: []trace.Attribute{trace.BoolAttribute("foo", true)}, }, { name: "false", key: "foo", value: false, want: []trace.Attribute{trace.BoolAttribute("foo", false)}, }, { name: "0", key: "foo", value: 0, want: []trace.Attribute{trace.Int64Attribute("foo", 0)}, }, { name: "42", key: "foo", value: 42, want: []trace.Attribute{trace.Int64Attribute("foo", 42)}, }, { name: "42.1", key: "foo", value: 42.1, want: []trace.Attribute{trace.Float64Attribute("foo", 42.1)}, }, { name: "short string", key: "foo", value: "bar", want: []trace.Attribute{trace.StringAttribute("foo", "bar")}, }, { name: "empty string", key: "foo", value: "", want: []trace.Attribute{trace.StringAttribute("foo", "")}, }, { name: "string length 255", key: "foo", value: strings.Repeat("a", 255), want: []trace.Attribute{trace.StringAttribute("foo", strings.Repeat("a", 255))}, }, { name: "string length 256", key: "foo", value: strings.Repeat("a", 256), want: []trace.Attribute{trace.StringAttribute("foo", strings.Repeat("a", 256))}, }, { name: "string length 257", key: "foo", value: strings.Repeat("a", 257), want: []trace.Attribute{ trace.StringAttribute("foo", strings.Repeat("a", 256)), trace.StringAttribute("foo.1", strings.Repeat("a", 1)), }, }, { name: "string length 511", key: "foo", value: strings.Repeat("a", 511), want: []trace.Attribute{ trace.StringAttribute("foo", strings.Repeat("a", 256)), trace.StringAttribute("foo.1", strings.Repeat("a", 255)), }, }, { name: "string length 512", key: "foo", value: strings.Repeat("a", 512), want: []trace.Attribute{ trace.StringAttribute("foo", strings.Repeat("a", 256)), trace.StringAttribute("foo.1", strings.Repeat("a", 256)), }, }, { name: "string length 513", key: "foo", value: strings.Repeat("a", 513), want: []trace.Attribute{ trace.StringAttribute("foo", strings.Repeat("a", 256)), trace.StringAttribute("foo.1", strings.Repeat("a", 256)), trace.StringAttribute("foo.2", strings.Repeat("a", 1)), }, }, { name: "unsupported type", key: "foo", value: unsupportedType{}, want: []trace.Attribute{ trace.StringAttribute("foo", "castToAttribute not implemented for type struct"), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := castToAttribute(tt.key, tt.value) //nolint if !reflect.DeepEqual(got, tt.want) { t.Errorf("castToAttribute() got = %v, want %v", got, tt.want) } }) } } labkit-v1.7.0/tracing/impl/static_tracer.go000066400000000000000000000007471410103036700207240ustar00rootroot00000000000000// +build tracer_static package impl import ( "fmt" "io" opentracing "github.com/opentracing/opentracing-go" ) // New will instantiate a new instance of the tracer, given the driver and configuration. func New(driverName string, config map[string]string) (opentracing.Tracer, io.Closer, error) { factory := registry[driverName] if factory == nil { return nil, nil, fmt.Errorf("tracer: unable to load driver %s: %w", driverName, ErrConfiguration) } return factory(config) } labkit-v1.7.0/tracing/impl/tracer_registry.go000066400000000000000000000004751410103036700213030ustar00rootroot00000000000000package impl import ( "io" opentracing "github.com/opentracing/opentracing-go" ) type tracerFactoryFunc func(config map[string]string) (opentracing.Tracer, io.Closer, error) var registry = map[string]tracerFactoryFunc{} func registerTracer(name string, factory tracerFactoryFunc) { registry[name] = factory } labkit-v1.7.0/tracing/inbound_http.go000066400000000000000000000030271410103036700176230ustar00rootroot00000000000000package tracing import ( "net/http" opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" "gitlab.com/gitlab-org/labkit/correlation" ) // Handler will extract tracing from inbound request. func Handler(h http.Handler, opts ...HandlerOption) http.Handler { config := applyHandlerOptions(opts) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if isHealthCheck(r) { h.ServeHTTP(w, r) return } tracer := opentracing.GlobalTracer() if tracer == nil { h.ServeHTTP(w, r) return } wireContext, _ := tracer.Extract( opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)) // Create the span referring to the RPC client if available. // If wireContext == nil, a root span will be created. additionalStartSpanOpts := []opentracing.StartSpanOption{ ext.RPCServerOption(wireContext), } correlationID := correlation.ExtractFromContext(r.Context()) if correlationID != "" { additionalStartSpanOpts = append(additionalStartSpanOpts, opentracing.Tag{Key: "correlation_id", Value: correlationID}) } serverSpan := opentracing.StartSpan( config.getOperationName(r), additionalStartSpanOpts..., ) serverSpan.SetTag("http.method", r.Method) serverSpan.SetTag("http.url", r.URL) defer serverSpan.Finish() ctx := opentracing.ContextWithSpan(r.Context(), serverSpan) h.ServeHTTP(w, r.WithContext(ctx)) }) } func isHealthCheck(r *http.Request) bool { return r.URL.Path == "/-/liveness" || r.URL.Path == "/-/readiness" } labkit-v1.7.0/tracing/inbound_http_options.go000066400000000000000000000021211410103036700213700ustar00rootroot00000000000000package tracing import ( "fmt" "net/http" ) // OperationNamer will return an operation name given an HTTP request. type OperationNamer func(*http.Request) string // The configuration for InjectCorrelationID. type handlerConfig struct { getOperationName OperationNamer } // HandlerOption will configure a correlation handler. type HandlerOption func(*handlerConfig) func applyHandlerOptions(opts []HandlerOption) handlerConfig { config := handlerConfig{ getOperationName: func(req *http.Request) string { // By default use `GET /x/y/z` for operation names return fmt.Sprintf("%s %s", req.Method, req.URL.Path) }, } for _, v := range opts { v(&config) } return config } // WithRouteIdentifier allows a RouteIdentifier attribute to be set in the handler. // This value will appear in the traces. func WithRouteIdentifier(routeIdentifier string) HandlerOption { return func(config *handlerConfig) { config.getOperationName = func(req *http.Request) string { // Use `GET routeIdentifier` for operation names return fmt.Sprintf("%s %s", req.Method, routeIdentifier) } } } labkit-v1.7.0/tracing/initialization.go000066400000000000000000000023601410103036700201540ustar00rootroot00000000000000package tracing import ( "io" opentracing "github.com/opentracing/opentracing-go" log "github.com/sirupsen/logrus" "gitlab.com/gitlab-org/labkit/tracing/connstr" "gitlab.com/gitlab-org/labkit/tracing/impl" ) type nopCloser struct { } func (nopCloser) Close() error { return nil } // Initialize will initialize distributed tracing. func Initialize(opts ...InitializationOption) io.Closer { config := applyInitializationOptions(opts) if config.connectionString == "" { // No opentracing connection has been set return &nopCloser{} } driverName, options, err := connstr.Parse(config.connectionString) if err != nil { log.WithError(err).Infoln("unable to parse connection") return &nopCloser{} } // URL-provided service_name overrides the InitializationOption if _, ok := options["service_name"]; !ok { options["service_name"] = config.serviceName } tracer, closer, err := impl.New(driverName, options) if err != nil { log.WithError(err).Warn("skipping tracing configuration step") return &nopCloser{} } if tracer == nil { log.Warn("no tracer provided, tracing will be disabled") } else { log.Info("Tracing enabled") opentracing.SetGlobalTracer(tracer) } if closer == nil { return &nopCloser{} } return closer } labkit-v1.7.0/tracing/initialization_options.go000066400000000000000000000022531410103036700217300ustar00rootroot00000000000000package tracing import ( "os" "path" ) const tracingEnvKey = "GITLAB_TRACING" // The configuration for InjectCorrelationID. type initializationConfig struct { serviceName string connectionString string } // InitializationOption will configure a correlation handler. type InitializationOption func(*initializationConfig) func applyInitializationOptions(opts []InitializationOption) initializationConfig { config := initializationConfig{ serviceName: path.Base(os.Args[0]), connectionString: os.Getenv(tracingEnvKey), } for _, v := range opts { v(&config) } return config } // WithServiceName allows the service name to be configured for the tracer // this will appear in traces. func WithServiceName(serviceName string) InitializationOption { return func(config *initializationConfig) { config.serviceName = serviceName } } // WithConnectionString allows the opentracing connection string to be overridden. By default // this will be retrieved from the GITLAB_TRACING environment variable. func WithConnectionString(connectionString string) InitializationOption { return func(config *initializationConfig) { config.connectionString = connectionString } } labkit-v1.7.0/tracing/outbound_http.go000066400000000000000000000074541410103036700200340ustar00rootroot00000000000000package tracing import ( "crypto/tls" "net/http" "net/http/httptrace" opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" otlog "github.com/opentracing/opentracing-go/log" logkit "gitlab.com/gitlab-org/labkit/log" ) type tracingRoundTripper struct { delegate http.RoundTripper config roundTripperConfig } func (c tracingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) { tracer := opentracing.GlobalTracer() if tracer == nil { return c.delegate.RoundTrip(req) } ctx := req.Context() var parentCtx opentracing.SpanContext parentSpan := opentracing.SpanFromContext(ctx) if parentSpan != nil { parentCtx = parentSpan.Context() } // start a new Span to wrap HTTP request span := opentracing.StartSpan( c.config.getOperationName(req), opentracing.ChildOf(parentCtx), ) defer span.Finish() ctx = opentracing.ContextWithSpan(ctx, span) // attach ClientTrace to the Context, and Context to request trace := newClientTrace(span) ctx = httptrace.WithClientTrace(ctx, trace) req = req.WithContext(ctx) ext.SpanKindRPCClient.Set(span) ext.HTTPUrl.Set(span, req.URL.String()) ext.HTTPMethod.Set(span, req.Method) carrier := opentracing.HTTPHeadersCarrier(req.Header) err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, carrier) if err != nil { logkit.ContextLogger(ctx).WithError(err).Error("tracing span injection failed") } response, err := c.delegate.RoundTrip(req) if err != nil { span.LogFields( otlog.String("event", "roundtrip error"), otlog.Object("error", err), ) } else { span.LogFields( otlog.String("event", "roundtrip complete"), otlog.Int("status", response.StatusCode), ) } return response, err } func newClientTrace(span opentracing.Span) *httptrace.ClientTrace { trace := &clientTrace{span: span} return &httptrace.ClientTrace{ GotFirstResponseByte: trace.gotFirstResponseByte, ConnectStart: trace.connectStart, ConnectDone: trace.connectDone, TLSHandshakeStart: trace.tlsHandshakeStart, TLSHandshakeDone: trace.tlsHandshakeDone, WroteHeaders: trace.wroteHeaders, WroteRequest: trace.wroteRequest, } } // clientTrace holds a reference to the Span and // provides methods used as ClientTrace callbacks. type clientTrace struct { span opentracing.Span } func (h *clientTrace) gotFirstResponseByte() { h.span.LogFields(otlog.String("event", "got first response byte")) } func (h *clientTrace) connectStart(network, addr string) { h.span.LogFields( otlog.String("event", "connect started"), otlog.String("network", network), otlog.String("addr", addr), ) } func (h *clientTrace) connectDone(network, addr string, err error) { h.span.LogFields( otlog.String("event", "connect done"), otlog.String("network", network), otlog.String("addr", addr), otlog.Object("error", err), ) } func (h *clientTrace) tlsHandshakeStart() { h.span.LogFields(otlog.String("event", "tls handshake started")) } func (h *clientTrace) tlsHandshakeDone(state tls.ConnectionState, err error) { h.span.LogFields( otlog.String("event", "tls handshake done"), otlog.Object("error", err), ) } func (h *clientTrace) wroteHeaders() { h.span.LogFields(otlog.String("event", "headers written")) } func (h *clientTrace) wroteRequest(info httptrace.WroteRequestInfo) { h.span.LogFields( otlog.String("event", "request written"), otlog.Object("error", info.Err), ) } // NewRoundTripper acts as a "client-middleware" for outbound http requests // adding instrumentation to the outbound request and then delegating to the underlying // transport. func NewRoundTripper(delegate http.RoundTripper, opts ...RoundTripperOption) http.RoundTripper { config := applyRoundTripperOptions(opts) return &tracingRoundTripper{delegate: delegate, config: config} } labkit-v1.7.0/tracing/outbound_http_options.go000066400000000000000000000012071410103036700215750ustar00rootroot00000000000000package tracing import ( "fmt" "net/http" ) // The configuration for InjectCorrelationID. type roundTripperConfig struct { getOperationName OperationNamer } // RoundTripperOption will configure a correlation handler. type RoundTripperOption func(*roundTripperConfig) func applyRoundTripperOptions(opts []RoundTripperOption) roundTripperConfig { config := roundTripperConfig{ getOperationName: func(req *http.Request) string { // By default use `GET https://localhost` for operation names return fmt.Sprintf("%s %s://%s", req.Method, req.URL.Scheme, req.URL.Host) }, } for _, v := range opts { v(&config) } return config }