├── version.txt ├── smoketest ├── bin │ └── .gitignore ├── activate.sh ├── get-kubectl.sh ├── test-k8s.sh ├── frontend-service.yaml └── frontend-deployment.yaml ├── OSSMETADATA ├── config ├── testdata │ ├── unknown_parsers.yaml │ ├── paths-only.yaml │ ├── labelselector-and-paths.yaml │ ├── basic.yaml │ └── parser_with_options.yaml ├── config_test.go └── config.go ├── LICENSES ├── github.com │ ├── hashicorp │ │ └── golang-lru │ │ │ ├── go.mod │ │ │ ├── testing.go │ │ │ ├── README.md │ │ │ ├── .gitignore │ │ │ ├── .golangci.yml │ │ │ ├── .github │ │ │ └── workflows │ │ │ │ └── ci.yml │ │ │ ├── doc.go │ │ │ └── simplelru │ │ │ └── lru_interface.go │ ├── honeycombio │ │ ├── libhoney-go │ │ │ └── NOTICE │ │ ├── urlshaper │ │ │ └── NOTICE │ │ ├── dynsampler-go │ │ │ └── NOTICE │ │ ├── honeytail │ │ │ └── httime │ │ │ │ └── NOTICE │ │ └── gonx │ │ │ └── LICENSE │ ├── davecgh │ │ └── go-spew │ │ │ └── spew │ │ │ └── LICENSE │ ├── mailru │ │ └── easyjson │ │ │ └── LICENSE │ ├── hpcloud │ │ └── tail │ │ │ ├── ratelimiter │ │ │ └── Licence │ │ │ └── LICENSE.txt │ ├── kr │ │ └── logfmt │ │ │ └── LICENSE │ ├── boltdb │ │ └── bolt │ │ │ └── LICENSE │ ├── fxamacker │ │ └── cbor │ │ │ └── v2 │ │ │ └── LICENSE │ ├── json-iterator │ │ └── go │ │ │ └── LICENSE │ ├── facebookgo │ │ ├── clock │ │ │ └── LICENSE │ │ ├── muster │ │ │ └── license │ │ └── limitgroup │ │ │ └── license │ ├── emicklei │ │ └── go-restful │ │ │ └── v3 │ │ │ └── LICENSE │ ├── josharian │ │ └── intern │ │ │ └── license.md │ ├── sirupsen │ │ └── logrus │ │ │ └── LICENSE │ ├── bmatcuk │ │ └── doublestar │ │ │ └── v4 │ │ │ └── LICENSE │ ├── mitchellh │ │ └── mapstructure │ │ │ └── LICENSE │ ├── x448 │ │ └── float16 │ │ │ └── LICENSE │ ├── klauspost │ │ └── compress │ │ │ ├── zstd │ │ │ └── internal │ │ │ │ └── xxhash │ │ │ │ └── LICENSE.txt │ │ │ └── internal │ │ │ └── snapref │ │ │ └── LICENSE │ ├── pkg │ │ └── errors │ │ │ └── LICENSE │ ├── vmihailenco │ │ ├── msgpack │ │ │ └── v5 │ │ │ │ └── LICENSE │ │ └── tagparser │ │ │ └── v2 │ │ │ └── LICENSE │ ├── google │ │ ├── uuid │ │ │ └── LICENSE │ │ └── go-cmp │ │ │ └── cmp │ │ │ └── LICENSE │ ├── golang │ │ └── protobuf │ │ │ └── LICENSE │ ├── jessevdk │ │ └── go-flags │ │ │ └── LICENSE │ ├── munnerz │ │ └── goautoneg │ │ │ └── LICENSE │ └── gogo │ │ └── protobuf │ │ └── LICENSE ├── gopkg.in │ ├── yaml.v2 │ │ └── NOTICE │ ├── yaml.v3 │ │ ├── NOTICE │ │ └── LICENSE │ ├── alexcesaro │ │ └── statsd.v2 │ │ │ └── LICENSE │ ├── inf.v0 │ │ └── LICENSE │ ├── fsnotify.v1 │ │ └── LICENSE │ └── tomb.v1 │ │ └── LICENSE ├── sigs.k8s.io │ └── yaml │ │ └── goyaml.v2 │ │ └── NOTICE ├── golang.org │ └── x │ │ ├── net │ │ └── LICENSE │ │ ├── term │ │ └── LICENSE │ │ ├── text │ │ └── LICENSE │ │ ├── sys │ │ └── unix │ │ │ └── LICENSE │ │ ├── oauth2 │ │ └── LICENSE │ │ └── time │ │ └── rate │ │ └── LICENSE ├── google.golang.org │ └── protobuf │ │ └── LICENSE └── k8s.io │ ├── utils │ └── internal │ │ └── third_party │ │ └── forked │ │ └── golang │ │ └── net │ │ └── LICENSE │ ├── apimachinery │ └── third_party │ │ └── forked │ │ └── golang │ │ └── reflect │ │ └── LICENSE │ └── kube-openapi │ └── pkg │ └── internal │ └── third_party │ └── go-json-experiment │ └── json │ └── LICENSE ├── static └── honeycomb-agent.png ├── e2e-tests ├── apihost │ ├── Dockerfile │ └── server.py ├── internal │ ├── Dockerfile │ └── test_internal.sh ├── README.md └── test.sh ├── .gitignore ├── CONTRIBUTING.md ├── SUPPORT.md ├── .github ├── CODEOWNERS ├── workflows │ ├── apply-labels.yml │ ├── add-to-project-v2.yml │ ├── release.yml │ ├── stale.yml │ └── validate-pr-title.yml ├── ISSUE_TEMPLATE │ ├── question-discussion.md │ ├── feature_request.md │ ├── security-vulnerability-report.md │ └── bug_report.md ├── release.yml ├── PULL_REQUEST_TEMPLATE.md └── dependabot.yml ├── event └── event.go ├── .ko.yaml ├── Dockerfile.in ├── metrics ├── mock │ └── mock_restclient.go ├── metrics.go ├── conventions.go ├── ttlcache.go ├── resource.go └── accumulator.go ├── processors ├── sample_test.go ├── scrub_test.go ├── drop.go ├── additional_fields.go ├── timefield_test.go ├── additional_fields_test.go ├── timefield.go ├── scrub.go ├── rename_field.go ├── keep_event.go ├── drop_event.go ├── route_event.go ├── processors.go ├── kubernetes.go ├── urlshaper.go ├── rename_field_test.go └── sample.go ├── parsers ├── nop.go ├── json.go ├── parsers.go ├── audit.go ├── regex_test.go ├── regex.go ├── regexp_extension.go ├── redis.go ├── keyval.go ├── glog.go └── nginx.go ├── unwrappers ├── raw.go ├── unwrappers.go ├── docker_json.go └── cri.go ├── tailer ├── state_test.go └── state.go ├── NOTICE ├── interval ├── interval_runner_test.go └── interval_runner.go ├── .editorconfig ├── kubelet ├── rest_client_test.go ├── metadata_provider.go ├── cert.go ├── stats_provider.go ├── rest_client.go ├── metadata_provider_test.go └── client.go ├── Makefile ├── RELEASING.md ├── version └── version.go ├── SECURITY.md ├── docs └── example-configurations.md ├── .circleci └── config.yml ├── transmission ├── ringbuffer.go └── ringbuffer_test.go ├── README.md ├── handlers └── handlers.go ├── go.mod └── examples └── quickstart.yaml /version.txt: -------------------------------------------------------------------------------- 1 | 2.7.4 2 | -------------------------------------------------------------------------------- /smoketest/bin/.gitignore: -------------------------------------------------------------------------------- 1 | kubectl* 2 | -------------------------------------------------------------------------------- /OSSMETADATA: -------------------------------------------------------------------------------- 1 | osslifecycle=maintenance 2 | -------------------------------------------------------------------------------- /config/testdata/unknown_parsers.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | watchers: 3 | - parser: 4 | - a 5 | - b 6 | -------------------------------------------------------------------------------- /smoketest/activate.sh: -------------------------------------------------------------------------------- 1 | kubectl_path=$(pwd)/bin 2 | 3 | export PATH="${kubectl_path}:${PATH}" 4 | -------------------------------------------------------------------------------- /LICENSES/github.com/hashicorp/golang-lru/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hashicorp/golang-lru 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /static/honeycomb-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honeycombio/honeycomb-kubernetes-agent/HEAD/static/honeycomb-agent.png -------------------------------------------------------------------------------- /config/testdata/paths-only.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiHost: https://api.honeycomb.io 3 | writekey: "asdf" 4 | watchers: 5 | - paths: ["test"] -------------------------------------------------------------------------------- /e2e-tests/apihost/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python 2 | RUN pip install flask zstd 3 | ADD ./server.py / 4 | ENTRYPOINT ["python", "server.py"] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | honeycomb-kubernetes-agent 2 | config-test.yaml 3 | /bin 4 | /.push-* 5 | /.container-* 6 | /.dockerfile-* 7 | kind 8 | .kube 9 | .idea/** 10 | /_local 11 | -------------------------------------------------------------------------------- /config/testdata/labelselector-and-paths.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiHost: https://api.honeycomb.io 3 | writekey: "asdf" 4 | watchers: 5 | - labelSelector: app=nginx 6 | paths: ["/var/log/pods/nginx*/*/*"] 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Please see our [general guide for OSS lifecycle and practices.](https://github.com/honeycombio/home/blob/main/honeycomb-oss-lifecycle-and-practices.md) 4 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # How to Get Help 2 | 3 | This project uses GitHub issues to track bugs, feature requests, and questions about using the project. Please search for existing issues before filing a new one. 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners file. 2 | # This file controls who is tagged for review for any given pull request. 3 | 4 | # For anything not explicitly taken by someone else: 5 | * @honeycombio/pipeline-team 6 | -------------------------------------------------------------------------------- /config/testdata/basic.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiHost: https://api.honeycomb.io 3 | writekey: "asdf" 4 | watchers: 5 | - labelSelector: app=nginx 6 | dataset: testdataset 7 | parser: json 8 | additionalFields: 9 | foo: 1 10 | bar: "2" -------------------------------------------------------------------------------- /event/event.go: -------------------------------------------------------------------------------- 1 | package event 2 | 3 | import "time" 4 | 5 | type Event struct { 6 | Dataset string 7 | Path string 8 | SampleRate uint 9 | Timestamp time.Time 10 | Data map[string]interface{} 11 | RawMessage string 12 | } 13 | -------------------------------------------------------------------------------- /e2e-tests/internal/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bitnami/kubectl:1.18 2 | USER 0 3 | RUN install_packages jq netcat 4 | RUN mkdir /.kube 5 | RUN chown 1001 .kube 6 | USER 1001 7 | COPY ./test_internal.sh / 8 | COPY ./testspec.yaml / 9 | COPY --chown=1001 ./.kube/config /.kube/config 10 | ENTRYPOINT ["/test_internal.sh"] 11 | -------------------------------------------------------------------------------- /LICENSES/github.com/hashicorp/golang-lru/testing.go: -------------------------------------------------------------------------------- 1 | package lru 2 | 3 | import ( 4 | "crypto/rand" 5 | "math" 6 | "math/big" 7 | "testing" 8 | ) 9 | 10 | func getRand(tb testing.TB) int64 { 11 | out, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) 12 | if err != nil { 13 | tb.Fatal(err) 14 | } 15 | return out.Int64() 16 | } 17 | -------------------------------------------------------------------------------- /.ko.yaml: -------------------------------------------------------------------------------- 1 | # KO_DOCKER_REPO should be either honeycombio (our dockerhub user), kind.local 2 | # (for KinD) or ko.local 3 | 4 | defaultBaseImage: gcr.io/distroless/static 5 | builds: 6 | - id: honeycomb-kubernetes-agent 7 | main: . 8 | ldflags: 9 | - -X github.com/honeycombio/honeycomb-kubernetes-agent/version.VERSION={{.Env.VERSION}} 10 | -------------------------------------------------------------------------------- /LICENSES/github.com/hashicorp/golang-lru/README.md: -------------------------------------------------------------------------------- 1 | golang-lru 2 | ========== 3 | 4 | Please upgrade to github.com/hashicorp/golang-lru/v2 for all new code as v1 will 5 | not be updated anymore. The v2 version supports generics and is faster; old code 6 | can specify a specific tag, e.g. github.com/hashicorp/golang-lru/v1.0.2 for 7 | backwards compatibility. 8 | -------------------------------------------------------------------------------- /Dockerfile.in: -------------------------------------------------------------------------------- 1 | 2 | FROM golang:alpine AS build 3 | 4 | RUN apk --update add ca-certificates 5 | 6 | 7 | FROM ARG_FROM 8 | 9 | LABEL maintainer="Team Honeycomb " 10 | 11 | COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 12 | ADD bin/ARG_ARCH/ARG_BIN /ARG_BIN 13 | 14 | ENTRYPOINT ["/ARG_BIN"] 15 | -------------------------------------------------------------------------------- /.github/workflows/apply-labels.yml: -------------------------------------------------------------------------------- 1 | name: Apply project labels 2 | on: [issues, pull_request_target, label] 3 | jobs: 4 | apply-labels: 5 | runs-on: ubuntu-latest 6 | name: Apply common project labels 7 | steps: 8 | - uses: honeycombio/oss-management-actions/labels@v1 9 | with: 10 | github-token: ${{ secrets.GITHUB_TOKEN }} 11 | -------------------------------------------------------------------------------- /metrics/mock/mock_restclient.go: -------------------------------------------------------------------------------- 1 | package metrics_mock 2 | 3 | import "io/ioutil" 4 | 5 | type MockRestClient struct { 6 | } 7 | 8 | func (f MockRestClient) StatsSummary() ([]byte, error) { 9 | return ioutil.ReadFile("../testdata/stats-summary.json") 10 | } 11 | 12 | func (f MockRestClient) Pods() ([]byte, error) { 13 | return ioutil.ReadFile("../testdata/pods.json") 14 | } 15 | -------------------------------------------------------------------------------- /LICENSES/github.com/hashicorp/golang-lru/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /config/testdata/parser_with_options.yaml: -------------------------------------------------------------------------------- 1 | watchers: 2 | - dataset: nginx 3 | parser: 4 | name: nginx 5 | options: 6 | log_format: default 7 | processors: 8 | - additional_fields: 9 | foo: "1" 10 | bar: 2 11 | - dataset: custom 12 | parser: 13 | name: regex 14 | options: 15 | expressions: 16 | - "foo" 17 | - "bar" 18 | -------------------------------------------------------------------------------- /e2e-tests/README.md: -------------------------------------------------------------------------------- 1 | This directory contains scripts to run a simple end-to-end smoke test of the 2 | agent inside KIND. It starts: 3 | 4 | - a sample nginx service 5 | - the Honeycomb agent, configured to slurp logs from the nginx service 6 | - a mock Honeycomb API server that the agent will talk to. 7 | 8 | It then issues a request to nginx and checks that the mock API server actually 9 | gets a corresponding event. 10 | -------------------------------------------------------------------------------- /processors/sample_test.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSamplerCanBuildsKeysWithInts(t *testing.T) { 11 | key := makeDynSampleKey(&event.Event{ 12 | Data: map[string]interface{}{ 13 | "status": 200, 14 | }, 15 | }, []string { "status"}) 16 | assert.Equal(t, "200", key) 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question-discussion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question/Discussion 3 | about: General question about how things work or a discussion 4 | title: '' 5 | labels: 'type: discussion' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | -------------------------------------------------------------------------------- /parsers/nop.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | // Doesn't do any parsing 4 | type NoOpParser struct{} 5 | 6 | func (p *NoOpParser) Parse(line string) (map[string]interface{}, error) { 7 | return map[string]interface{}{"log": line}, nil 8 | } 9 | 10 | type NoOpParserFactory struct{} 11 | 12 | func (pf *NoOpParserFactory) Init(options map[string]interface{}) error { return nil } 13 | 14 | func (pf *NoOpParserFactory) New() Parser { 15 | return &NoOpParser{} 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project-v2.yml: -------------------------------------------------------------------------------- 1 | name: Add to project 2 | on: 3 | issues: 4 | types: [opened] 5 | pull_request_target: 6 | types: [opened] 7 | jobs: 8 | add-to-project: 9 | runs-on: ubuntu-latest 10 | name: Add issues and PRs to project 11 | steps: 12 | - uses: actions/add-to-project@main 13 | with: 14 | project-url: https://github.com/orgs/honeycombio/projects/27 15 | github-token: ${{ secrets.GHPROJECTS_TOKEN }} 16 | -------------------------------------------------------------------------------- /smoketest/get-kubectl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=$1 4 | 5 | if [ -z "${VERSION}" ]; then 6 | echo "usage: $(basename $0) " 7 | echo "version = 1.x.x" 8 | exit 1; 9 | fi; 10 | 11 | if [ "$(uname)" == "Darwin" ]; then 12 | curl -L https://storage.googleapis.com/kubernetes-release/release/v${VERSION}/bin/darwin/amd64/kubectl -o bin/kubectl-${VERSION} 13 | else 14 | curl -L https://storage.googleapis.com/kubernetes-release/release/v${VERSION}/bin/linux/amd64/kubectl -o bin/kubectl-${VERSION} 15 | 16 | fi; 17 | -------------------------------------------------------------------------------- /unwrappers/raw.go: -------------------------------------------------------------------------------- 1 | package unwrappers 2 | 3 | import ( 4 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 5 | "github.com/honeycombio/honeycomb-kubernetes-agent/parsers" 6 | ) 7 | 8 | type RawLogUnwrapper struct{} 9 | 10 | func (u *RawLogUnwrapper) Unwrap(rawLine string, parser parsers.Parser) (*event.Event, error) { 11 | 12 | data, err := parser.Parse(rawLine) 13 | if err != nil { 14 | return nil, err 15 | } 16 | if data == nil { 17 | return nil, nil 18 | } 19 | return &event.Event{Data: data, RawMessage: rawLine}, nil 20 | } 21 | -------------------------------------------------------------------------------- /parsers/json.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | import "encoding/json" 4 | 5 | // Parses line as JSON 6 | type JSONParser struct{} 7 | 8 | func (p *JSONParser) Parse(line string) (map[string]interface{}, error) { 9 | data := make(map[string]interface{}) 10 | err := json.Unmarshal([]byte(line), &data) 11 | if err != nil { 12 | return nil, err 13 | } 14 | return data, nil 15 | } 16 | 17 | type JSONParserFactory struct{} 18 | 19 | func (pf *JSONParserFactory) Init(options map[string]interface{}) error { return nil } 20 | 21 | func (pf *JSONParserFactory) New() Parser { 22 | return &JSONParser{} 23 | } 24 | -------------------------------------------------------------------------------- /tailer/state_test.go: -------------------------------------------------------------------------------- 1 | package tailer 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestStateRecorder(t *testing.T) { 11 | stateFileHandle, err := ioutil.TempFile("/tmp", "honeycomb-agent-state") 12 | assert.NoError(t, err) 13 | stateFile := stateFileHandle.Name() 14 | sr, err := NewStateRecorder(stateFile) 15 | assert.NoError(t, err) 16 | err = sr.Record("/var/log/wherever", 22) 17 | assert.NoError(t, err) 18 | offset, err := sr.Get("/var/log/wherever") 19 | assert.NoError(t, err) 20 | assert.Equal(t, offset, int64(22)) 21 | } 22 | -------------------------------------------------------------------------------- /smoketest/test-k8s.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=$1 4 | 5 | if [ -z "${VERSION}" ]; then 6 | echo "usage: $(basename $0) " 7 | echo "version = 1.x.x" 8 | exit 1; 9 | fi; 10 | 11 | ./get-kubectl.sh ${VERSION} 12 | 13 | rm -f bin/kubectl 14 | 15 | chmod +x bin/kubectl-${VERSION} 16 | 17 | cd bin 18 | 19 | ln -s kubectl-${VERSION} kubectl 20 | 21 | cd - 22 | 23 | 24 | minikube delete 25 | 26 | minikube start --kubernetes-version v${VERSION} 27 | 28 | # remove taint on master so that we can schedule pods on it 29 | bin/kubectl taint nodes minikube node-role.kubernetes.io/master:NoSchedule- 30 | 31 | -------------------------------------------------------------------------------- /LICENSES/github.com/hashicorp/golang-lru/.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - megacheck 4 | - revive 5 | - govet 6 | - unconvert 7 | - megacheck 8 | - gas 9 | - gocyclo 10 | - dupl 11 | - misspell 12 | - unparam 13 | - unused 14 | - typecheck 15 | - ineffassign 16 | - stylecheck 17 | - exportloopref 18 | - gocritic 19 | - nakedret 20 | - gosimple 21 | - prealloc 22 | fast: false 23 | disable-all: true 24 | 25 | issues: 26 | exclude-rules: 27 | - path: _test\.go 28 | linters: 29 | - dupl 30 | exclude-use-default: false 31 | -------------------------------------------------------------------------------- /processors/scrub_test.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 9 | ) 10 | 11 | func TestScrubField(t *testing.T) { 12 | fp := FieldScrubber{} 13 | fp.Init(map[string]interface{}{"Field": "foo"}) 14 | 15 | ev := &event.Event{} 16 | ev.Data = map[string]interface{}{"foo": "hidden"} 17 | 18 | fp.Process(ev) 19 | 20 | scrubbedVal, ok := ev.Data["foo"] 21 | 22 | assert.True(t, ok) 23 | 24 | assert.Equal(t, scrubbedVal, "e564b4081d7a9ea4b00dada53bdae70c99b87b6fce869f0c3dd4d2bfa1e53e1c") 25 | } 26 | -------------------------------------------------------------------------------- /LICENSES/gopkg.in/yaml.v2/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2016 Canonical Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSES/gopkg.in/yaml.v3/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2016 Canonical Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /smoketest/frontend-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: frontend 5 | annotations: 6 | service.beta.kubernetes.io/aws-load-balancer-internal: 10.0.0.0/12 7 | labels: 8 | app: guestbook 9 | tier: frontend 10 | spec: 11 | # comment or delete the following line if you want to use a LoadBalancer 12 | type: NodePort 13 | # if your cluster supports it, uncomment the following to automatically create 14 | # an external load-balanced IP for the frontend service. 15 | # type: LoadBalancer 16 | ports: 17 | - port: 80 18 | selector: 19 | app: guestbook 20 | tier: frontend 21 | -------------------------------------------------------------------------------- /LICENSES/sigs.k8s.io/yaml/goyaml.v2/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2016 Canonical Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | exclude: 5 | labels: 6 | - no-changelog 7 | categories: 8 | - title: 💥 Breaking Changes 💥 9 | labels: 10 | - "version: bump major" 11 | - breaking-change 12 | - title: 💡 Enhancements 13 | labels: 14 | - "type: enhancement" 15 | - title: 🐛 Fixes 16 | labels: 17 | - "type: bug" 18 | - title: 🛠 Maintenance 19 | labels: 20 | - "type: maintenance" 21 | - "type: dependencies" 22 | - "type: documentation" 23 | - title: 🤷 Other Changes 24 | labels: 25 | - "*" 26 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-Present Honeycomb, Hound Technology, Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /interval/interval_runner_test.go: -------------------------------------------------------------------------------- 1 | // Originally inspired by OpenTelemetry Collector kubeletstats receiver 2 | // https://github.com/open-telemetry/opentelemetry-collector 3 | 4 | package interval 5 | 6 | import ( 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestScheduler(t *testing.T) { 12 | f := &fakeRunnable{} 13 | s := NewRunner("test", time.Second, f) 14 | go func() { 15 | _ = s.Start() 16 | }() 17 | s.Stop() 18 | // getting here is success 19 | } 20 | 21 | type fakeRunnable struct { 22 | } 23 | 24 | func (t *fakeRunnable) Setup() error { 25 | return nil 26 | } 27 | 28 | func (fakeRunnable) Run() error { 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'type: enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | 16 | **Is your feature request related to a problem? Please describe.** 17 | 18 | 19 | **Describe the solution you'd like** 20 | 21 | 22 | **Describe alternatives you've considered** 23 | 24 | 25 | **Additional context** 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security-vulnerability-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security vulnerability report 3 | about: Let us know if you discover a security vulnerability 4 | title: '' 5 | labels: 'type: security' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | **Versions** 16 | 17 | 18 | 19 | **Description** 20 | 21 | (Please include any relevant CVE advisory links) 22 | -------------------------------------------------------------------------------- /processors/drop.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 5 | "github.com/mitchellh/mapstructure" 6 | ) 7 | 8 | type FieldDropper struct { 9 | config *fieldDropperConfig 10 | } 11 | 12 | type fieldDropperConfig struct { 13 | Field string 14 | } 15 | 16 | func (f *FieldDropper) Init(options map[string]interface{}) error { 17 | config := &fieldDropperConfig{} 18 | err := mapstructure.Decode(options, config) 19 | if err != nil { 20 | return err 21 | } 22 | f.config = config 23 | return nil 24 | } 25 | 26 | func (f *FieldDropper) Process(ev *event.Event) bool { 27 | delete(ev.Data, f.config.Field) 28 | return true 29 | } 30 | -------------------------------------------------------------------------------- /LICENSES/github.com/honeycombio/libhoney-go/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-Present Honeycomb, Hound Technology, Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSES/github.com/honeycombio/urlshaper/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-Present Honeycomb, Hound Technology, Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # When opening a file, EditorConfig plugins look for a file named .editorconfig 4 | # in the directory of the opened file and in every parent directory. 5 | # A search for .editorconfig files will stop if the root filepath is reached 6 | # or an EditorConfig file with root=true is found. 7 | 8 | # EditorConfig files are read top to bottom and the most recent rules found 9 | # take precedence. Properties from matching EditorConfig sections are applied 10 | # in the order they were read, so properties in closer files take precedence. 11 | 12 | # go files 13 | [*.go] 14 | 15 | indent_size = 8 16 | indent_style = tab 17 | tab_width = 8 -------------------------------------------------------------------------------- /LICENSES/github.com/honeycombio/dynsampler-go/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-Present Honeycomb, Hound Technology, Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSES/github.com/honeycombio/honeytail/httime/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-Present Honeycomb, Hound Technology, Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | ## Which problem is this PR solving? 14 | 15 | - 16 | 17 | ## Short description of the changes 18 | 19 | - 20 | -------------------------------------------------------------------------------- /kubelet/rest_client_test.go: -------------------------------------------------------------------------------- 1 | // Originally inspired by OpenTelemetry Collector kubeletstats receiver 2 | // https://github.com/open-telemetry/opentelemetry-collector 3 | 4 | package kubelet 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestRestClient(t *testing.T) { 13 | rest := NewRestClient(&fakeClient{}) 14 | resp, _ := rest.StatsSummary() 15 | require.Equal(t, "/stats/summary", string(resp)) 16 | resp, _ = rest.Pods() 17 | require.Equal(t, "/pods", string(resp)) 18 | } 19 | 20 | var _ Client = (*fakeClient)(nil) 21 | 22 | type fakeClient struct{} 23 | 24 | func (f *fakeClient) Get(path string) ([]byte, error) { 25 | return []byte(path), nil 26 | } 27 | -------------------------------------------------------------------------------- /processors/additional_fields.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 5 | ) 6 | 7 | type AdditionalFieldsProcessor struct { 8 | AdditionalFields map[string]interface{} 9 | } 10 | 11 | func (a *AdditionalFieldsProcessor) Init(options map[string]interface{}) error { 12 | // options expects a map of string->values 13 | if options != nil { 14 | a.AdditionalFields = options 15 | } else { 16 | a.AdditionalFields = make(map[string]interface{}) 17 | } 18 | return nil 19 | } 20 | 21 | func (a *AdditionalFieldsProcessor) Process(ev *event.Event) bool { 22 | for k, v := range a.AdditionalFields { 23 | ev.Data[k] = v 24 | } 25 | 26 | return true 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create helm chart issue on release 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: 6 | jobs: 7 | create_issue: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Create an issue 11 | uses: actions-ecosystem/action-create-issue@v1 12 | with: 13 | github_token: ${{ secrets.GHPROJECTS_TOKEN }} 14 | repo: honeycombio/helm-charts 15 | title: Bump Honeycomb Kubernetes Agent to Latest Version 16 | body: | 17 | ## Bump Honeycomb Kubernetes Agent 18 | 19 | Update Honeycomb Kubernetes Agent to latest version 20 | 21 | labels: | 22 | type: dependencies 23 | status: oncall 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAKEFLAGS += --warn-undefined-variables 2 | MAKEFLAGS += --no-builtin-rules 3 | MAKEFLAGS += --no-builtin-variables 4 | 5 | .PHONY: install-tools 6 | install-tools: 7 | go get -tool github.com/google/go-licenses/v2@v2.0.0-alpha.1 8 | go mod tidy 9 | 10 | .PHONY: update-licenses 11 | update-licenses: install-tools 12 | rm -rf LICENSES; \ 13 | go tool go-licenses save . --save_path LICENSES; 14 | 15 | .PHONY: verify-licenses 16 | verify-licenses: install-tools 17 | go tool go-licenses save . --save_path temp; \ 18 | if diff temp LICENSES > /dev/null; then \ 19 | echo "Passed"; \ 20 | rm -rf temp; \ 21 | else \ 22 | echo "LICENSES directory must be updated. Run make update-licenses"; \ 23 | rm -rf temp; \ 24 | exit 1; \ 25 | fi; \ 26 | -------------------------------------------------------------------------------- /e2e-tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euxo pipefail 3 | 4 | curl -C - -Lo kind https://github.com/kubernetes-sigs/kind/releases/download/v0.11.1/kind-linux-amd64 && chmod +x kind 5 | export KUBECONFIG=$(dirname $0)/internal/.kube/config 6 | ./kind delete cluster 7 | ./kind create cluster --wait 300s 8 | 9 | # Build the agent and mock API host images 10 | VERSION=$(cat version.txt | tr -d '\n') 11 | 12 | docker build -t apihost:test $(dirname $0)/apihost 13 | docker tag ko.local/honeycomb-kubernetes-agent:$VERSION \ 14 | honeycombio/honeycomb-kubernetes-agent:test 15 | ./kind load docker-image apihost:test 16 | ./kind load docker-image honeycombio/honeycomb-kubernetes-agent:test 17 | 18 | docker build -t internal:test $(dirname $0)/internal 19 | docker run --network=kind --rm --name internal internal:test 20 | -------------------------------------------------------------------------------- /processors/timefield_test.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | // Ensure that the timestamp processor can handle timestamp fields of type time.Time 12 | func TestTimefieldProcessReturnsTime(t *testing.T) { 13 | // Format should be irrelevant here, we just return the field if it's there 14 | tp := TimeFieldExtractor{config: &timeFieldConfig{Field: "special_time_field", Format: time.RFC3339}} 15 | 16 | mockTime := time.Now() 17 | ev := &event.Event{} 18 | assert.True(t, ev.Timestamp.IsZero()) 19 | ev.Data = map[string]interface{}{"foo": "some_value", "special_time_field": mockTime} 20 | tp.Process(ev) 21 | assert.Equal(t, mockTime, ev.Timestamp) 22 | } 23 | -------------------------------------------------------------------------------- /e2e-tests/apihost/server.py: -------------------------------------------------------------------------------- 1 | # Super simple mock Honeycomb API server 2 | import collections 3 | import flask 4 | import sys 5 | import json 6 | import zstd 7 | import io 8 | 9 | app = flask.Flask(__name__) 10 | 11 | events = collections.defaultdict(list) 12 | 13 | 14 | @app.route("/1/batch/", methods=['POST']) 15 | def receive_events(dataset): 16 | data = flask.request.data 17 | if flask.request.headers.get("Content-Encoding") == "zstd": 18 | data = zstd.decompress(data) 19 | data = json.loads(data) 20 | events[dataset].extend(data) 21 | resp = len(data) * [{"status": 202}] 22 | return flask.jsonify(resp) 23 | 24 | 25 | @app.route("/") 26 | def return_calls(): 27 | return flask.jsonify(events) 28 | 29 | 30 | if __name__ == '__main__': 31 | app.run(host='0.0.0.0') 32 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Creating a new release 2 | 3 | 1. Update the version string in `version.txt`. 4 | 2. Update the version in `quickstart.yaml`. 5 | 3. Add new release notes to `CHANGELOG.md`. 6 | 4. Commit changes, push, and open a release preparation pull request for review 7 | 5. Once the pull request is approved and merged, fetch the updated `main` branch 8 | 6. Add a tag to the `main` branch with the new version in the following format: `v1.2.3`. 9 | 7. Push the new version tag up to the project repository to kick off the CI workflow, which will package and publish the image to Docker Hub. 10 | 8. Update the Draft Release with proper release notes (copied from CHANGELOG or auto-generated). 11 | 9. Update the [Honeycomb Helm Chart](https://github.com/honeycombio/helm-charts/tree/main/charts/honeycomb) with the changes 12 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package version 18 | 19 | // VERSION is the app-global version string, which should be substituted with a 20 | // real value during build. 21 | var VERSION = "UNKNOWN" 22 | -------------------------------------------------------------------------------- /processors/additional_fields_test.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAdditionalFieldsProcess(t *testing.T) { 11 | fields := map[string]interface{}{"foo": 1, "bar": "two"} 12 | 13 | p := &AdditionalFieldsProcessor{} 14 | p.Init(fields) 15 | 16 | ev := &event.Event{} 17 | ev.Data = make(map[string]interface{}) 18 | p.Process(ev) 19 | assert.Equal(t, ev.Data["foo"], 1) 20 | assert.Equal(t, ev.Data["bar"], "two") 21 | } 22 | 23 | func TestAdditionalFieldsProcessNilMap(t *testing.T) { 24 | p := &AdditionalFieldsProcessor{} 25 | p.Init(nil) 26 | 27 | ev := &event.Event{} 28 | ev.Data = make(map[string]interface{}) 29 | // shouldn't crash 30 | p.Process(ev) 31 | } 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Let us know if something is not working as expected 4 | title: '' 5 | labels: 'type: bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 17 | 18 | **Versions** 19 | 20 | 21 | 22 | **Steps to reproduce** 23 | 24 | 1. 25 | 26 | **Additional context** 27 | -------------------------------------------------------------------------------- /processors/timefield.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 5 | "github.com/honeycombio/honeytail/httime" 6 | "github.com/mitchellh/mapstructure" 7 | ) 8 | 9 | type timeFieldConfig struct { 10 | Field string 11 | Format string 12 | } 13 | 14 | type TimeFieldExtractor struct { 15 | config *timeFieldConfig 16 | } 17 | 18 | func (t *TimeFieldExtractor) Init(options map[string]interface{}) error { 19 | config := &timeFieldConfig{} 20 | err := mapstructure.Decode(options, config) 21 | if err != nil { 22 | return err 23 | } 24 | t.config = config 25 | return nil 26 | } 27 | 28 | func (t *TimeFieldExtractor) Process(ev *event.Event) bool { 29 | ev.Timestamp = httime.GetTimestamp( 30 | ev.Data, 31 | t.config.Field, 32 | t.config.Format) 33 | return true 34 | } 35 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | labels: 13 | - "type: dependencies" 14 | reviewers: 15 | - "honeycombio/pipeline-team" 16 | groups: 17 | minor-patch: 18 | update-types: 19 | - "minor" 20 | - "patch" 21 | commit-message: 22 | prefix: "maint" 23 | include: "scope" 24 | -------------------------------------------------------------------------------- /LICENSES/github.com/davecgh/go-spew/spew/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2012-2016 Dave Collins 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /metrics/metrics.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import "time" 4 | 5 | type ResourceMetrics struct { 6 | Resource *Resource 7 | Metrics 8 | } 9 | 10 | type Metrics map[string]*Metric 11 | 12 | type MetricType int 13 | 14 | const ( 15 | MetricTypeInt MetricType = 1 16 | MetricTypeFloat MetricType = 2 17 | ) 18 | 19 | type Metric struct { 20 | Type MetricType 21 | IsCounter bool 22 | IntValue *uint64 23 | FloatValue *float64 24 | } 25 | 26 | func (m *Metric) GetValue() float64 { 27 | if m.Type == MetricTypeInt { 28 | if m.IntValue == nil { 29 | return 0 30 | } else { 31 | return float64(*m.IntValue) 32 | } 33 | } 34 | 35 | if m.FloatValue == nil { 36 | return 0 37 | } else { 38 | return *m.FloatValue 39 | } 40 | } 41 | 42 | type CounterValue struct { 43 | Key string 44 | Timestamp time.Time 45 | Value *Metric 46 | } 47 | -------------------------------------------------------------------------------- /LICENSES/github.com/hashicorp/golang-lru/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | tags: 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: set up go 1.19 15 | uses: actions/setup-go@v1 16 | with: 17 | go-version: 1.19 18 | id: go 19 | 20 | - name: checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: build and test 24 | run: | 25 | go test -timeout=60s -race 26 | go build -race 27 | 28 | - name: install golangci-lint 29 | run: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $GITHUB_WORKSPACE v1.50.1 30 | 31 | - name: run golangci-lint 32 | run: $GITHUB_WORKSPACE/golangci-lint run --out-format=github-actions 33 | -------------------------------------------------------------------------------- /kubelet/metadata_provider.go: -------------------------------------------------------------------------------- 1 | // Originally inspired by OpenTelemetry Collector kubeletstats receiver 2 | // https://github.com/open-telemetry/opentelemetry-collector 3 | 4 | package kubelet 5 | 6 | import ( 7 | "encoding/json" 8 | 9 | v1 "k8s.io/api/core/v1" 10 | ) 11 | 12 | // MetadataProvider wraps a RestClient, returning an unmarshalled metadata. 13 | type MetadataProvider struct { 14 | rc RestClient 15 | } 16 | 17 | func NewMetadataProvider(rc RestClient) *MetadataProvider { 18 | return &MetadataProvider{rc: rc} 19 | } 20 | 21 | // Pods calls the /pods endpoint and unmarshalls the 22 | // results into a v1.PodList struct. 23 | func (p *MetadataProvider) Pods() (*v1.PodList, error) { 24 | pods, err := p.rc.Pods() 25 | if err != nil { 26 | return nil, err 27 | } 28 | var out v1.PodList 29 | err = json.Unmarshal(pods, &out) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return &out, nil 34 | } 35 | -------------------------------------------------------------------------------- /processors/scrub.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | 7 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 8 | "github.com/mitchellh/mapstructure" 9 | ) 10 | 11 | type FieldScrubber struct { 12 | config *fieldScrubberConfig 13 | } 14 | 15 | type fieldScrubberConfig struct { 16 | Field string 17 | } 18 | 19 | func (f *FieldScrubber) Init(options map[string]interface{}) error { 20 | config := &fieldScrubberConfig{} 21 | err := mapstructure.Decode(options, config) 22 | if err != nil { 23 | return err 24 | } 25 | f.config = config 26 | return nil 27 | } 28 | 29 | func (f *FieldScrubber) Process(ev *event.Event) bool { 30 | if val, ok := ev.Data[f.config.Field]; ok { 31 | // generate a sha256 hash and use the base16 for the content 32 | newVal := sha256.Sum256([]byte(fmt.Sprintf("%v", val))) 33 | ev.Data[f.config.Field] = fmt.Sprintf("%x", newVal) 34 | } 35 | return true 36 | } 37 | -------------------------------------------------------------------------------- /kubelet/cert.go: -------------------------------------------------------------------------------- 1 | // Originally inspired by OpenTelemetry Collector kubeletstats receiver 2 | // https://github.com/open-telemetry/opentelemetry-collector 3 | 4 | package kubelet 5 | 6 | import ( 7 | "crypto/x509" 8 | "io/ioutil" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | func systemCertPoolPlusPath(certPath string) (*x509.CertPool, error) { 14 | sysCerts, err := x509.SystemCertPool() 15 | if err != nil { 16 | return nil, errors.WithMessage(err, "Could not load system x509 cert pool") 17 | } 18 | return certPoolPlusPath(sysCerts, certPath) 19 | } 20 | 21 | func certPoolPlusPath(certPool *x509.CertPool, certPath string) (*x509.CertPool, error) { 22 | certBytes, err := ioutil.ReadFile(certPath) 23 | if err != nil { 24 | return nil, errors.Wrapf(err, "Cert path %s could not be read", certPath) 25 | } 26 | ok := certPool.AppendCertsFromPEM(certBytes) 27 | if !ok { 28 | return nil, errors.New("AppendCertsFromPEM failed") 29 | } 30 | return certPool, nil 31 | } 32 | -------------------------------------------------------------------------------- /kubelet/stats_provider.go: -------------------------------------------------------------------------------- 1 | // Originally inspired by OpenTelemetry Collector kubeletstats receiver 2 | // https://github.com/open-telemetry/opentelemetry-collector 3 | 4 | package kubelet 5 | 6 | import ( 7 | "encoding/json" 8 | 9 | stats "k8s.io/kubelet/pkg/apis/stats/v1alpha1" 10 | ) 11 | 12 | // StatsProvider wraps a RestClient, returning an unmarshalled 13 | // stats.Summary struct from the kubelet API. 14 | type StatsProvider struct { 15 | rc RestClient 16 | } 17 | 18 | func NewStatsProvider(rc RestClient) *StatsProvider { 19 | return &StatsProvider{rc: rc} 20 | } 21 | 22 | // StatsSummary calls the /stats/summary kubelet endpoint and unmarshalls the 23 | // results into a stats.Summary struct. 24 | func (p *StatsProvider) StatsSummary() (*stats.Summary, error) { 25 | summary, err := p.rc.StatsSummary() 26 | if err != nil { 27 | return nil, err 28 | } 29 | var out stats.Summary 30 | err = json.Unmarshal(summary, &out) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return &out, nil 35 | } 36 | -------------------------------------------------------------------------------- /kubelet/rest_client.go: -------------------------------------------------------------------------------- 1 | // Originally inspired by OpenTelemetry Collector kubeletstats receiver 2 | // https://github.com/open-telemetry/opentelemetry-collector 3 | 4 | package kubelet 5 | 6 | // RestClient is swappable for testing. 7 | type RestClient interface { 8 | StatsSummary() ([]byte, error) 9 | Pods() ([]byte, error) 10 | } 11 | 12 | // RestClient is a thin wrapper around a kubelet client, encapsulating endpoints 13 | // and their corresponding http methods. The endpoints /stats/container /spec/ 14 | // are excluded because they require cadvisor. The /metrics endpoint is excluded 15 | // because it returns Prometheus data. 16 | type HTTPRestClient struct { 17 | client Client 18 | } 19 | 20 | func NewRestClient(client Client) *HTTPRestClient { 21 | return &HTTPRestClient{client: client} 22 | } 23 | 24 | func (c *HTTPRestClient) StatsSummary() ([]byte, error) { 25 | return c.client.Get("/stats/summary") 26 | } 27 | 28 | func (c *HTTPRestClient) Pods() ([]byte, error) { 29 | return c.client.Get("/pods") 30 | } 31 | -------------------------------------------------------------------------------- /LICENSES/github.com/hashicorp/golang-lru/doc.go: -------------------------------------------------------------------------------- 1 | // Package lru provides three different LRU caches of varying sophistication. 2 | // 3 | // Cache is a simple LRU cache. It is based on the 4 | // LRU implementation in groupcache: 5 | // https://github.com/golang/groupcache/tree/master/lru 6 | // 7 | // TwoQueueCache tracks frequently used and recently used entries separately. 8 | // This avoids a burst of accesses from taking out frequently used entries, 9 | // at the cost of about 2x computational overhead and some extra bookkeeping. 10 | // 11 | // ARCCache is an adaptive replacement cache. It tracks recent evictions as 12 | // well as recent usage in both the frequent and recent caches. Its 13 | // computational overhead is comparable to TwoQueueCache, but the memory 14 | // overhead is linear with the size of the cache. 15 | // 16 | // ARC has been patented by IBM, so do not use it if that is problematic for 17 | // your program. 18 | // 19 | // All caches in this package take locks while operating, and are therefore 20 | // thread-safe for consumers. 21 | package lru 22 | -------------------------------------------------------------------------------- /LICENSES/github.com/mailru/easyjson/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Mail.Ru Group 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /LICENSES/github.com/hpcloud/tail/ratelimiter/Licence: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 99designs 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /LICENSES/github.com/kr/logfmt/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 Keith Rarick, Blake Mizerany 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /unwrappers/unwrappers.go: -------------------------------------------------------------------------------- 1 | // Package unwrappers contains support for handling log transport formats. 2 | // How's this different from a parser? You can think of unwrappers as handling 3 | // extra structure that's added by log *transport* formats, e.g., by the Docker 4 | // json-file log driver, or by syslog. 5 | package unwrappers 6 | 7 | import ( 8 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 9 | "github.com/honeycombio/honeycomb-kubernetes-agent/parsers" 10 | ) 11 | 12 | type Unwrapper interface { 13 | Unwrap(string, parsers.Parser) (*event.Event, error) 14 | } 15 | 16 | type InferUnwrapper struct{ 17 | raw RawLogUnwrapper 18 | json DockerJSONLogUnwrapper 19 | cri CriLogUnwrapper 20 | } 21 | 22 | func (w *InferUnwrapper) Unwrap(s string, p parsers.Parser) (*event.Event, error) { 23 | if len(s) > 0 && s[0] == '{' { 24 | // Scan for the start of a JSON blob. 25 | return w.json.Unwrap(s, p) 26 | } else if len(s) > 10 && s[4] == '-' && s[7] == '-' && s[10] == 'T' { 27 | // Scan for what looks like an RFC3339 timestamp. 28 | return w.cri.Unwrap(s, p) 29 | } else { 30 | // Treat as raw. 31 | return w.raw.Unwrap(s, p) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSES/github.com/boltdb/bolt/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Ben Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LICENSES/github.com/fxamacker/cbor/v2/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present Faye Amacker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /LICENSES/github.com/json-iterator/go/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 json-iterator 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/github.com/facebookgo/clock/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ben Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/github.com/honeycombio/gonx/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Anton Egorov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LICENSES/github.com/emicklei/go-restful/v3/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012,2013 Ernest Micklei 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSES/github.com/josharian/intern/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Josh Bleecher Snyder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/github.com/sirupsen/logrus/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Simon Eskildsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/gopkg.in/alexcesaro/statsd.v2/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alexandre Cesaro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LICENSES/github.com/bmatcuk/doublestar/v4/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Bob Matcuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /LICENSES/github.com/mitchellh/mapstructure/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Mitchell Hashimoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/github.com/x448/float16/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /LICENSES/github.com/klauspost/compress/zstd/internal/xxhash/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Caleb Spare 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /LICENSES/github.com/hpcloud/tail/LICENSE.txt: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | # © Copyright 2015 Hewlett Packard Enterprise Development LP 4 | Copyright (c) 2014 ActiveState 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /smoketest/frontend-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1beta2 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: frontend 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: guestbook 9 | tier: frontend 10 | replicas: 1 11 | template: 12 | metadata: 13 | labels: 14 | app: guestbook 15 | tier: frontend 16 | spec: 17 | containers: 18 | - name: php-redis 19 | image: gcr.io/google-samples/gb-frontend:v4 20 | resources: 21 | requests: 22 | cpu: 100m 23 | memory: 100Mi 24 | env: 25 | - name: GET_HOSTS_FROM 26 | value: dns 27 | # Using `GET_HOSTS_FROM=dns` requires your cluster to 28 | # provide a dns service. As of Kubernetes 1.3, DNS is a built-in 29 | # service launched automatically. However, if the cluster you are using 30 | # does not have a built-in DNS service, you can instead 31 | # instead access an environment variable to find the master 32 | # service's host. To do so, comment out the 'value: dns' line above, and 33 | # uncomment the line below: 34 | # value: env 35 | ports: 36 | - containerPort: 80 37 | -------------------------------------------------------------------------------- /parsers/parsers.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/honeycombio/honeycomb-kubernetes-agent/config" 7 | ) 8 | 9 | type Parser interface { 10 | Parse(line string) (map[string]interface{}, error) 11 | } 12 | 13 | type ParserFactory interface { 14 | Init(options map[string]interface{}) error 15 | New() Parser 16 | } 17 | 18 | func NewParserFactory(config *config.ParserConfig) (ParserFactory, error) { 19 | var factory ParserFactory 20 | switch config.Name { 21 | case "json": 22 | factory = &JSONParserFactory{} 23 | case "nop": 24 | factory = &NoOpParserFactory{} 25 | case "nginx", "envoy", "nginx-ingress": 26 | factory = &NginxParserFactory{ 27 | // Default log format depends on the parser name specified in 28 | // configuration 29 | parserName: config.Name, 30 | } 31 | case "glog": 32 | factory = &GlogParserFactory{} 33 | case "redis": 34 | factory = &RedisParserFactory{} 35 | case "keyval": 36 | factory = &KeyvalParserFactory{} 37 | case "audit": 38 | factory = &AuditParserFactory{} 39 | case "regex": 40 | factory = &RegexFactory{} 41 | default: 42 | return nil, fmt.Errorf("Unknown parser type %s", config.Name) 43 | } 44 | err := factory.Init(config.Options) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return factory, nil 49 | } 50 | -------------------------------------------------------------------------------- /unwrappers/docker_json.go: -------------------------------------------------------------------------------- 1 | package unwrappers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 10 | "github.com/honeycombio/honeycomb-kubernetes-agent/parsers" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type dockerJSONLogLine struct { 15 | Log string 16 | Stream string 17 | Time string 18 | } 19 | 20 | type DockerJSONLogUnwrapper struct{} 21 | 22 | func (u *DockerJSONLogUnwrapper) Unwrap(rawLine string, parser parsers.Parser) (*event.Event, error) { 23 | line := &dockerJSONLogLine{} 24 | err := json.Unmarshal([]byte(rawLine), line) 25 | if err != nil { 26 | logrus.WithError(err).Info("Error parsing docker JSON line") 27 | return nil, fmt.Errorf("Error parsing log line as Docker json-file log: %v", err) 28 | } 29 | line.Log = strings.TrimRight(line.Log, "\n") 30 | 31 | data, err := parser.Parse(line.Log) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | if data == nil { 37 | return nil, nil 38 | } 39 | 40 | ts, err := time.Parse(time.RFC3339Nano, line.Time) 41 | if err != nil { 42 | logrus.WithError(err).Info("Error parsing docker JSON timestamp") 43 | } 44 | 45 | return &event.Event{ 46 | Data: data, 47 | Timestamp: ts, 48 | RawMessage: line.Log, 49 | }, nil 50 | } 51 | -------------------------------------------------------------------------------- /processors/rename_field.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 7 | "github.com/mitchellh/mapstructure" 8 | ) 9 | 10 | var ( 11 | ErrFieldOptionUnspecified = errors.New("rename_field processor requires both 'new' and 'original' field names to be set") 12 | ErrFieldOptionsMatch = errors.New("rename_field processor does not support matching 'new' and 'original' field names") 13 | ) 14 | 15 | type FieldRenamer struct { 16 | config *fieldRenamerConfig 17 | } 18 | 19 | type fieldRenamerConfig struct { 20 | Original string 21 | New string 22 | } 23 | 24 | func (f *FieldRenamer) Init(options map[string]interface{}) error { 25 | config := &fieldRenamerConfig{} 26 | err := mapstructure.Decode(options, config) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | if config.New == "" || config.Original == "" { 32 | return ErrFieldOptionUnspecified 33 | } 34 | if config.New == config.Original { 35 | return ErrFieldOptionsMatch 36 | } 37 | f.config = config 38 | return nil 39 | } 40 | 41 | func (f *FieldRenamer) Process(ev *event.Event) bool { 42 | if ev.Data != nil { 43 | if field_data, found := ev.Data[f.config.Original]; found { 44 | ev.Data[f.config.New] = field_data 45 | delete(ev.Data, f.config.Original) 46 | } 47 | } 48 | return true 49 | } 50 | -------------------------------------------------------------------------------- /parsers/audit.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | import lru "github.com/hashicorp/golang-lru" 4 | 5 | var keyvalOptions = map[string]interface{}{"prefixRegex": "(?P[0-9TZ:+.-]+) AUDIT: "} 6 | 7 | const cacheSize = 128 8 | 9 | type AuditParserFactory struct { 10 | keyvalParserFactory *KeyvalParserFactory 11 | } 12 | 13 | func (pf *AuditParserFactory) Init(options map[string]interface{}) error { 14 | pf.keyvalParserFactory = &KeyvalParserFactory{} 15 | pf.keyvalParserFactory.Init(keyvalOptions) 16 | return nil 17 | } 18 | 19 | func (pf *AuditParserFactory) New() Parser { 20 | cache, _ := lru.New(cacheSize) 21 | return &AuditParser{ 22 | keyvalParser: pf.keyvalParserFactory.New(), 23 | cache: cache, 24 | } 25 | } 26 | 27 | type AuditParser struct { 28 | keyvalParser Parser 29 | cache *lru.Cache 30 | } 31 | 32 | func (p *AuditParser) Parse(line string) (map[string]interface{}, error) { 33 | data, err := p.keyvalParser.Parse(line) 34 | if err != nil { 35 | return nil, err 36 | } 37 | if id, ok := data["id"]; ok { 38 | if cached, ok := p.cache.Peek(id); ok { 39 | if prior, ok := cached.(map[string]interface{}); ok { 40 | p.cache.Remove(id) 41 | for k, v := range prior { 42 | data[k] = v 43 | } 44 | return data, nil 45 | } 46 | } else { 47 | p.cache.Add(id, data) 48 | return nil, nil 49 | } 50 | } 51 | return data, nil 52 | } 53 | -------------------------------------------------------------------------------- /LICENSES/github.com/pkg/errors/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Dave Cheney 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /unwrappers/cri.go: -------------------------------------------------------------------------------- 1 | package unwrappers 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 9 | "github.com/honeycombio/honeycomb-kubernetes-agent/parsers" 10 | "github.com/sirupsen/logrus" 11 | ) 12 | 13 | type criLogLine struct { 14 | Time string 15 | Stream string 16 | Tags string 17 | Log string 18 | } 19 | 20 | type CriLogUnwrapper struct{} 21 | 22 | // 2020-04-04T03:20:26.7063258Z stdout F {rest of message follows} 23 | 24 | func (u *CriLogUnwrapper) Unwrap(rawLine string, parser parsers.Parser) (*event.Event, error) { 25 | line := &criLogLine{} 26 | parts := strings.SplitN(rawLine, " ", 4) 27 | if len(parts) != 4 { 28 | logrus.Info("Error parsing CRI line") 29 | return nil, fmt.Errorf("Error parsing log line as CRI log: '%s'", line) 30 | } 31 | line.Time = parts[0] 32 | line.Stream = parts[1] 33 | line.Tags = parts[2] 34 | line.Log = parts[3] 35 | line.Log = strings.TrimRight(line.Log, "\n") 36 | 37 | data, err := parser.Parse(line.Log) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | if data == nil { 43 | return nil, nil 44 | } 45 | 46 | ts, err := time.Parse(time.RFC3339Nano, line.Time) 47 | if err != nil { 48 | logrus.WithError(err).Info("Error parsing CRI timestamp") 49 | } 50 | 51 | return &event.Event{ 52 | Data: data, 53 | Timestamp: ts, 54 | RawMessage: line.Log, 55 | }, nil 56 | } 57 | -------------------------------------------------------------------------------- /processors/keep_event.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "fmt" 5 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 6 | "github.com/mitchellh/mapstructure" 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type EventKeeper struct { 11 | config *eventKeeperConfig 12 | values map[string]struct{} 13 | } 14 | 15 | type eventKeeperConfig struct { 16 | Field string 17 | Values []string 18 | } 19 | 20 | func (f *EventKeeper) Init(options map[string]interface{}) error { 21 | config := &eventKeeperConfig{} 22 | err := mapstructure.Decode(options, config) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | if config.Field == "" { 28 | return ErrFilterOptionUnspecified 29 | } 30 | f.config = config 31 | 32 | f.values = make(map[string]struct{}, len(f.config.Values)) 33 | for _, val := range f.config.Values { 34 | f.values[val] = struct{}{} 35 | } 36 | return nil 37 | } 38 | 39 | func (f *EventKeeper) Process(ev *event.Event) bool { 40 | if ev.Data == nil { 41 | return true 42 | } 43 | 44 | val, ok := ev.Data[f.config.Field] 45 | if !ok { 46 | return true 47 | } 48 | 49 | valString, ok := val.(string) 50 | if !ok { 51 | logrus.WithFields(logrus.Fields{ 52 | "key": f.config.Field, 53 | "value": val, 54 | "type": fmt.Sprintf("%T", val), 55 | }). 56 | Debug("Not filtering field of non-string type") 57 | return true 58 | } 59 | _, exists := f.values[valString] 60 | return exists 61 | } 62 | -------------------------------------------------------------------------------- /LICENSES/github.com/vmihailenco/msgpack/v5/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The github.com/vmihailenco/msgpack Authors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /LICENSES/github.com/vmihailenco/tagparser/v2/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 The github.com/vmihailenco/tagparser Authors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /LICENSES/github.com/hashicorp/golang-lru/simplelru/lru_interface.go: -------------------------------------------------------------------------------- 1 | // Package simplelru provides simple LRU implementation based on build-in container/list. 2 | package simplelru 3 | 4 | // LRUCache is the interface for simple LRU cache. 5 | type LRUCache interface { 6 | // Adds a value to the cache, returns true if an eviction occurred and 7 | // updates the "recently used"-ness of the key. 8 | Add(key, value interface{}) bool 9 | 10 | // Returns key's value from the cache and 11 | // updates the "recently used"-ness of the key. #value, isFound 12 | Get(key interface{}) (value interface{}, ok bool) 13 | 14 | // Checks if a key exists in cache without updating the recent-ness. 15 | Contains(key interface{}) (ok bool) 16 | 17 | // Returns key's value without updating the "recently used"-ness of the key. 18 | Peek(key interface{}) (value interface{}, ok bool) 19 | 20 | // Removes a key from the cache. 21 | Remove(key interface{}) bool 22 | 23 | // Removes the oldest entry from cache. 24 | RemoveOldest() (interface{}, interface{}, bool) 25 | 26 | // Returns the oldest entry from the cache. #key, value, isFound 27 | GetOldest() (interface{}, interface{}, bool) 28 | 29 | // Returns a slice of the keys in the cache, from oldest to newest. 30 | Keys() []interface{} 31 | 32 | // Returns the number of items in the cache. 33 | Len() int 34 | 35 | // Clears all cache entries. 36 | Purge() 37 | 38 | // Resizes cache, returning number evicted 39 | Resize(int) int 40 | } 41 | -------------------------------------------------------------------------------- /parsers/regex_test.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/honeycombio/honeycomb-kubernetes-agent/config" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRegexParser(t *testing.T) { 11 | cfg := &config.ParserConfig{ 12 | Name: "regex", Options: map[string]interface{}{ 13 | "expressions": []interface{}{ 14 | "(?P[A-z]+) (?P[0-9]{2}[0-9]?)", 15 | "(?P[A-z ]+),(?P[A-z]{2})", 16 | }, 17 | }, 18 | } 19 | 20 | pf, err := NewParserFactory(cfg) 21 | assert.Nil(t, err) 22 | parser := pf.New() 23 | 24 | tc := []struct { 25 | line string 26 | expected map[string]interface{} 27 | err bool 28 | }{ 29 | {line: "walnut 55", expected: map[string]interface{}{"species": "walnut", "height": "55"}, err: false}, 30 | {line: "douglas 105", expected: map[string]interface{}{"species": "douglas", "height": "105"}, err: false}, 31 | {line: "San Francisco,Ca", expected: map[string]interface{}{"city": "San Francisco", "state": "Ca"}, err: false}, 32 | {line: "South Lake Tahoe,CA", expected: map[string]interface{}{"city": "South Lake Tahoe", "state": "CA"}, err: false}, 33 | {line: "the quick brown fox jumped over the lazy dog", expected: nil, err: true}, 34 | } 35 | 36 | for _, tt := range tc { 37 | parsed, err := parser.Parse(tt.line) 38 | assert.Equal(t, parsed, tt.expected) 39 | if tt.err { 40 | assert.NotNil(t, err) 41 | } else { 42 | assert.Nil(t, err) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | name: 'Close stale issues and PRs' 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | 14 | steps: 15 | - uses: actions/stale@v4 16 | with: 17 | start-date: '2021-09-01T00:00:00Z' 18 | stale-issue-message: 'Marking this issue as stale because it has been open 14 days with no activity. Please add a comment if this is still an ongoing issue; otherwise this issue will be automatically closed in 7 days.' 19 | stale-pr-message: 'Marking this PR as stale because it has been open 30 days with no activity. Please add a comment if this PR is still relevant; otherwise this PR will be automatically closed in 7 days.' 20 | close-issue-message: 'Closing this issue due to inactivity. Please see our [Honeycomb OSS Lifecyle and Practices](https://github.com/honeycombio/home/blob/main/honeycomb-oss-lifecycle-and-practices.md).' 21 | close-pr-message: 'Closing this PR due to inactivity. Please see our [Honeycomb OSS Lifecyle and Practices](https://github.com/honeycombio/home/blob/main/honeycomb-oss-lifecycle-and-practices.md).' 22 | days-before-issue-stale: 14 23 | days-before-pr-stale: 30 24 | days-before-issue-close: 7 25 | days-before-pr-close: 7 26 | any-of-labels: 'status: info needed,status: revision needed' 27 | -------------------------------------------------------------------------------- /parsers/regex.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | type RegexFactory struct { 9 | expressions []string 10 | } 11 | 12 | func (rf *RegexFactory) Init(options map[string]interface{}) error { 13 | if options == nil { 14 | return fmt.Errorf("regex parser specified but no options defined") 15 | } 16 | 17 | expressions, ok := options["expressions"].([]interface{}) 18 | if !ok { 19 | return fmt.Errorf("regex parser missing patterns option") 20 | } 21 | 22 | rf.expressions = make([]string, len(expressions)) 23 | for i, s := range expressions { 24 | expression, ok := s.(string) 25 | if !ok { 26 | return fmt.Errorf("expected expression %s to be string", s) 27 | } 28 | rf.expressions[i] = expression 29 | } 30 | return nil 31 | } 32 | 33 | func (rf *RegexFactory) New() Parser { 34 | re := make([]*extRegexp, len(rf.expressions)) 35 | for i, p := range rf.expressions { 36 | re[i] = &extRegexp{regexp.MustCompile(p)} 37 | } 38 | return &RegexParser{re: re} 39 | } 40 | 41 | type RegexParser struct { 42 | re []*extRegexp 43 | } 44 | 45 | func (rp *RegexParser) Parse(line string) (map[string]interface{}, error) { 46 | var captures map[string]interface{} 47 | 48 | for _, re := range rp.re { 49 | _, captures = re.FindStringSubmatchIfaceMap(line) 50 | if captures != nil { 51 | break 52 | } 53 | } 54 | 55 | if captures == nil { 56 | return nil, fmt.Errorf("Couldn't parse line with any supplied regexes: %s", line) 57 | } 58 | return captures, nil 59 | } 60 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | This security policy applies to public projects under the [honeycombio organization][gh-organization] on GitHub. 4 | For security reports involving the services provided at `(ui|ui-eu|api|api-eu).honeycomb.io`, refer to the [Honeycomb Bug Bounty Program][bugbounty] for scope, expectations, and reporting procedures. 5 | 6 | ## Security/Bugfix Versions 7 | 8 | Security and bug fixes are generally provided only for the last minor version. 9 | Fixes are released either as part of the next minor version or as an on-demand patch version. 10 | 11 | Security fixes are given priority and might be enough to cause a new version to be released. 12 | 13 | ## Reporting a Vulnerability 14 | 15 | We encourage responsible disclosure of security vulnerabilities. 16 | If you find something suspicious, we encourage and appreciate your report! 17 | 18 | ### Ways to report 19 | 20 | In order for the vulnerability reports to reach maintainers as soon as possible, the preferred way is to use the "Report a vulnerability" button under the "Security" tab of the associated GitHub project. 21 | This creates a private communication channel between the reporter and the maintainers. 22 | 23 | If you are absolutely unable to or have strong reasons not to use GitHub's vulnerability reporting workflow, please reach out to the Honeycomb security team at [security@honeycomb.io](mailto:security@honeycomb.io). 24 | 25 | [gh-organization]: https://github.com/honeycombio 26 | [bugbounty]: https://www.honeycomb.io/bugbountyprogram 27 | -------------------------------------------------------------------------------- /processors/drop_event.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 7 | "github.com/mitchellh/mapstructure" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | var ( 12 | ErrFilterOptionUnspecified = errors.New("drop_event processor requires a 'Field' to be set") 13 | ) 14 | 15 | type EventDropper struct { 16 | config *eventDropperConfig 17 | values map[string]struct{} 18 | } 19 | 20 | type eventDropperConfig struct { 21 | Field string 22 | Values []string 23 | } 24 | 25 | func (f *EventDropper) Init(options map[string]interface{}) error { 26 | config := &eventDropperConfig{} 27 | err := mapstructure.Decode(options, config) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | if config.Field == "" { 33 | return ErrFilterOptionUnspecified 34 | } 35 | f.config = config 36 | 37 | f.values = make(map[string]struct{}, len(f.config.Values)) 38 | for _, val := range f.config.Values { 39 | f.values[val] = struct{}{} 40 | } 41 | return nil 42 | } 43 | 44 | func (f *EventDropper) Process(ev *event.Event) bool { 45 | if ev.Data == nil { 46 | return true 47 | } 48 | val, ok := ev.Data[f.config.Field] 49 | if !ok { 50 | return true 51 | } 52 | valString, ok := val.(string) 53 | if !ok { 54 | logrus.WithFields(logrus.Fields{ 55 | "key": f.config.Field, 56 | "value": val, 57 | "type": fmt.Sprintf("%T", val)}). 58 | Debug("Not filtering field of non-string type") 59 | return true 60 | } 61 | _, exists := f.values[valString] 62 | return !exists 63 | } 64 | -------------------------------------------------------------------------------- /LICENSES/golang.org/x/net/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/golang.org/x/term/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/golang.org/x/text/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/golang.org/x/sys/unix/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/golang.org/x/oauth2/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/github.com/google/uuid/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009,2014 Google Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/golang.org/x/time/rate/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/google.golang.org/protobuf/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/github.com/google/go-cmp/cmp/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/github.com/golang/protobuf/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2010 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /LICENSES/k8s.io/utils/internal/third_party/forked/golang/net/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/github.com/klauspost/compress/internal/snapref/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 The Snappy-Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/k8s.io/apimachinery/third_party/forked/golang/reflect/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/github.com/jessevdk/go-flags/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Jesse van den Kieboom. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following disclaimer 10 | in the documentation and/or other materials provided with the 11 | distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived from 14 | this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /LICENSES/gopkg.in/inf.v0/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Péter Surányi. Portions Copyright (c) 2009 The Go 2 | Authors. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /LICENSES/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSES/github.com/facebookgo/muster/license: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For muster software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /LICENSES/gopkg.in/fsnotify.v1/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | Copyright (c) 2012 fsnotify Authors. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /LICENSES/github.com/facebookgo/limitgroup/license: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For limitgroup software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestParsing(t *testing.T) { 11 | testFiles := []struct { 12 | fileName string 13 | valid bool 14 | }{ 15 | {"basic.yaml", true}, 16 | {"parser_with_options.yaml", true}, 17 | {"unknown_parsers.yaml", false}, 18 | {"labelselector-and-paths.yaml", false}, 19 | {"paths-only.yaml", true}, 20 | } 21 | for _, tc := range testFiles { 22 | path, _ := filepath.Abs(filepath.Join("testdata", tc.fileName)) 23 | _, err := ReadFromFile(path) 24 | if tc.valid { 25 | assert.NoError(t, err) 26 | } else { 27 | assert.Error(t, err) 28 | } 29 | } 30 | } 31 | 32 | func TestAdditionalFieldsParsing(t *testing.T) { 33 | path, _ := filepath.Abs(filepath.Join("testdata", "basic.yaml")) 34 | c, err := ReadFromFile(path) 35 | assert.NoError(t, err) 36 | 37 | assert.Equal(t, map[string]interface{}{"foo": 1, "bar": "2"}, c.AdditionalFields) 38 | 39 | path, _ = filepath.Abs(filepath.Join("testdata", "parser_with_options.yaml")) 40 | c, err = ReadFromFile(path) 41 | assert.NoError(t, err) 42 | 43 | assert.Equal(t, map[string]interface{}{"foo": "1", "bar": 2}, c.Watchers[0].Processors[0]["additional_fields"]) 44 | } 45 | 46 | func TestRegexExpressionsArrayParsing(t *testing.T) { 47 | path, _ := filepath.Abs(filepath.Join("testdata", "parser_with_options.yaml")) 48 | c, err := ReadFromFile(path) 49 | assert.NoError(t, err) 50 | 51 | assert.Equal(t, "regex", c.Watchers[1].Parser.Name) 52 | assert.Equal(t, map[string]interface{}{"expressions": []interface{}{"foo", "bar"}}, c.Watchers[1].Parser.Options) 53 | } 54 | -------------------------------------------------------------------------------- /LICENSES/github.com/munnerz/goautoneg/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Open Knowledge Foundation Ltd. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in 13 | the documentation and/or other materials provided with the 14 | distribution. 15 | 16 | Neither the name of the Open Knowledge Foundation Ltd. nor the 17 | names of its contributors may be used to endorse or promote 18 | products derived from this software without specific prior written 19 | permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /LICENSES/gopkg.in/tomb.v1/LICENSE: -------------------------------------------------------------------------------- 1 | tomb - support for clean goroutine termination in Go. 2 | 3 | Copyright (c) 2010-2011 - Gustavo Niemeyer 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 23 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /docs/example-configurations.md: -------------------------------------------------------------------------------- 1 | Here are some example configurations for the Honeycomb agent: 2 | 3 | 4 | Parse logs from pods labelled with `app: nginx`: 5 | ``` 6 | --- 7 | writekey: "YOUR_HONEYCOMB_WRITEKEY_HERE" 8 | watchers: 9 | - labelSelector: app=nginx 10 | parser: nginx 11 | dataset: nginx-kubernetes 12 | 13 | processors: 14 | - request_shape: 15 | field: request 16 | ``` 17 | 18 | Send logs from different services to different datasets: 19 | ``` 20 | writekey: "YOUR_HONEYCOMB_WRITEKEY_HERE" 21 | watchers: 22 | - labelSelector: "app=nginx" 23 | parser: nginx 24 | dataset: nginx-kubernetes 25 | 26 | - labelSelector: "app=frontend-web" 27 | parser: json 28 | dataset: frontend 29 | ``` 30 | 31 | 32 | Sample events from a `frontend-web` deployment: only send one in 20 events from 33 | the `prod` namespace, and one in 10 events from the `staging` namespace. 34 | ``` 35 | writekey: "YOUR_HONEYCOMB_WRITEKEY_HERE" 36 | watchers: 37 | - labelSelector: "app=frontend-web" 38 | namespace: prod 39 | parser: json 40 | dataset: frontend 41 | 42 | processors: 43 | - sample: 44 | type: static 45 | rate: 20 46 | - drop_field: 47 | field: user_email 48 | 49 | - labelSelector: "app=frontend-web" 50 | namespace: staging 51 | parser: json 52 | dataset: frontend 53 | 54 | processors: 55 | - sample: 56 | type: static 57 | rate: 10 58 | ``` 59 | 60 | Only process logs from the `sidecar` container in a multi-container pod: 61 | ``` 62 | --- 63 | writekey: "YOUR_HONEYCOMB_WRITEKEY_HERE" 64 | watchers: 65 | - labelSelector: "app=frontend-web" 66 | containerName: sidecar 67 | parser: json 68 | dataset: frontend 69 | ``` 70 | -------------------------------------------------------------------------------- /parsers/regexp_extension.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | import "regexp" 4 | 5 | // From https://github.com/honeycombio/honeytail/blob/master/parsers/extregexp.go, 6 | // but we don't need to vendor all of honeytail. 7 | // extRegexp is a Regexp with one additional method to make it easier to work 8 | // with named groups 9 | type extRegexp struct { 10 | *regexp.Regexp 11 | } 12 | 13 | func newExtRegexp(expr string) (*extRegexp, error) { 14 | re, err := regexp.Compile(expr) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return &extRegexp{re}, nil 19 | } 20 | 21 | // FindStringSubmatchMap behaves the same as FindStringSubmatch except instead 22 | // of a list of matches with the names separate, it returns the full match and a 23 | // map of named submatches 24 | func (r *extRegexp) FindStringSubmatchMap(s string) (string, map[string]string) { 25 | match := r.FindStringSubmatch(s) 26 | if match == nil { 27 | return "", nil 28 | } 29 | 30 | captures := make(map[string]string) 31 | for i, name := range r.SubexpNames() { 32 | if i == 0 { 33 | continue 34 | } 35 | if name != "" { 36 | // ignore unnamed matches 37 | captures[name] = match[i] 38 | } 39 | } 40 | return match[0], captures 41 | } 42 | 43 | // FindStringSubmatchMapIfaceMap behaves the same as FindStringSubmatchMap except it 44 | // returns a map[string]interface{} 45 | func (r *extRegexp) FindStringSubmatchIfaceMap(s string) (string, map[string]interface{}) { 46 | match := r.FindStringSubmatch(s) 47 | if match == nil { 48 | return "", nil 49 | } 50 | 51 | captures := make(map[string]interface{}) 52 | for i, name := range r.SubexpNames() { 53 | if i == 0 { 54 | continue 55 | } 56 | if name != "" { 57 | // ignore unnamed matches 58 | captures[name] = match[i] 59 | } 60 | } 61 | return match[0], captures 62 | } 63 | -------------------------------------------------------------------------------- /LICENSES/github.com/gogo/protobuf/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, The GoGo Authors. All rights reserved. 2 | 3 | Protocol Buffers for Go with Gadgets 4 | 5 | Go support for Protocol Buffers - Google's data interchange format 6 | 7 | Copyright 2010 The Go Authors. All rights reserved. 8 | https://github.com/golang/protobuf 9 | 10 | Redistribution and use in source and binary forms, with or without 11 | modification, are permitted provided that the following conditions are 12 | met: 13 | 14 | * Redistributions of source code must retain the above copyright 15 | notice, this list of conditions and the following disclaimer. 16 | * Redistributions in binary form must reproduce the above 17 | copyright notice, this list of conditions and the following disclaimer 18 | in the documentation and/or other materials provided with the 19 | distribution. 20 | * Neither the name of Google Inc. nor the names of its 21 | contributors may be used to endorse or promote products derived from 22 | this software without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 28 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 29 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 30 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 34 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | 36 | -------------------------------------------------------------------------------- /kubelet/metadata_provider_test.go: -------------------------------------------------------------------------------- 1 | // Originally inspired by OpenTelemetry Collector kubeletstats receiver 2 | // https://github.com/open-telemetry/opentelemetry-collector 3 | 4 | package kubelet 5 | 6 | import ( 7 | "io/ioutil" 8 | "testing" 9 | 10 | "github.com/pkg/errors" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | type testRestClient struct { 16 | fail bool 17 | invalidJSON bool 18 | } 19 | 20 | func (f testRestClient) StatsSummary() ([]byte, error) { 21 | return []byte{}, nil 22 | } 23 | 24 | func (f testRestClient) Pods() ([]byte, error) { 25 | if f.fail { 26 | return []byte{}, errors.New("failed") 27 | } 28 | if f.invalidJSON { 29 | return []byte("wrong-json-body"), nil 30 | } 31 | 32 | return ioutil.ReadFile("../testdata/pods.json") 33 | } 34 | 35 | func TestPods(t *testing.T) { 36 | tests := []struct { 37 | name string 38 | client RestClient 39 | wantError string 40 | }{ 41 | { 42 | name: "success", 43 | client: &testRestClient{}, 44 | wantError: "", 45 | }, 46 | { 47 | name: "failure", 48 | client: &testRestClient{fail: true}, 49 | wantError: "failed", 50 | }, 51 | { 52 | name: "invalid-json", 53 | client: &testRestClient{invalidJSON: true}, 54 | wantError: "invalid character 'w' looking for beginning of value", 55 | }, 56 | } 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | metadataProvider := NewMetadataProvider(tt.client) 60 | podsMetadata, err := metadataProvider.Pods() 61 | if tt.wantError == "" { 62 | require.NoError(t, err) 63 | require.Less(t, 0, len(podsMetadata.Items)) 64 | } else { 65 | if err == nil { 66 | assert.Fail(t, "err is nil", tt.name) 67 | } else { 68 | assert.Equal(t, tt.wantError, err.Error()) 69 | } 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tailer/state.go: -------------------------------------------------------------------------------- 1 | package tailer 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/boltdb/bolt" 9 | ) 10 | 11 | const bucketName = "honeycomb-agent-state" 12 | 13 | type StateRecorder interface { 14 | Record(path string, offset int64) error 15 | Get(path string) (int64, error) 16 | Delete(path string) error 17 | } 18 | 19 | type StateRecorderImpl struct { 20 | db *bolt.DB 21 | } 22 | 23 | func NewStateRecorder(stateFilePath string) (StateRecorder, error) { 24 | db, err := bolt.Open(stateFilePath, 0600, &bolt.Options{Timeout: 2 * time.Second}) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &StateRecorderImpl{ 29 | db: db, 30 | }, nil 31 | } 32 | 33 | func (s *StateRecorderImpl) Record(path string, offset int64) error { 34 | err := s.db.Update(func(tx *bolt.Tx) error { 35 | bucket := tx.Bucket([]byte(bucketName)) 36 | if bucket == nil { 37 | b, err := tx.CreateBucket([]byte(bucketName)) 38 | if err != nil { 39 | return err 40 | } 41 | bucket = b 42 | } 43 | err := bucket.Put([]byte(path), []byte(strconv.FormatInt(offset, 10))) 44 | return err 45 | }) 46 | return err 47 | } 48 | 49 | func (s *StateRecorderImpl) Get(path string) (offset int64, err error) { 50 | s.db.View(func(tx *bolt.Tx) error { 51 | b := tx.Bucket([]byte(bucketName)) 52 | if b == nil { 53 | err = errors.New("bucket not found") 54 | return err 55 | } 56 | v := b.Get([]byte(path)) 57 | if v == nil { 58 | err = errors.New("key not found") 59 | return err 60 | } 61 | offset, err = strconv.ParseInt(string(v), 10, 0) 62 | return err 63 | }) 64 | return offset, err 65 | } 66 | 67 | func (s *StateRecorderImpl) Delete(path string) (err error) { 68 | return s.db.Update(func(tx *bolt.Tx) error { 69 | b := tx.Bucket([]byte(bucketName)) 70 | if b == nil { 71 | return nil 72 | } 73 | return b.Delete([]byte(path)) 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /processors/route_event.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 7 | "github.com/mitchellh/mapstructure" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | type EventRouter struct { 12 | config *eventRouterConfig 13 | routes map[string]string 14 | } 15 | 16 | type eventRouterConfig struct { 17 | Field string 18 | Routes []eventRoute 19 | } 20 | 21 | type eventRoute struct { 22 | Dataset string 23 | Value string 24 | } 25 | 26 | var ( 27 | ErrDuplicateEventRoute = errors.New("route_event requires all values to be unique") 28 | ErrMissingFieldEventRoute = errors.New("route_event requires field to be set") 29 | ) 30 | 31 | func (f *EventRouter) Init(options map[string]interface{}) error { 32 | config := &eventRouterConfig{} 33 | err := mapstructure.Decode(options, config) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | if config.Field == "" { 39 | return ErrMissingFieldEventRoute 40 | } 41 | f.routes = make(map[string]string, len(config.Routes)) 42 | 43 | for _, route := range config.Routes { 44 | // Same route pointing to multiple datasets? 45 | if _, ok := f.routes[route.Value]; ok { 46 | return ErrDuplicateEventRoute 47 | } 48 | f.routes[route.Value] = route.Dataset 49 | } 50 | f.config = config 51 | return nil 52 | } 53 | 54 | func (f *EventRouter) Process(ev *event.Event) bool { 55 | if ev.Data == nil { 56 | return true 57 | } 58 | val, ok := ev.Data[f.config.Field] 59 | if !ok { 60 | return true 61 | } 62 | 63 | valString, ok := val.(string) 64 | if !ok { 65 | logrus.WithFields(logrus.Fields{ 66 | "key": f.config.Field, 67 | "value": val, 68 | "type": fmt.Sprintf("%T", val)}). 69 | Debug("Not routing field of non-string type") 70 | return true 71 | } 72 | if dataset, ok := f.routes[valString]; ok { 73 | ev.Dataset = dataset 74 | } 75 | return true 76 | } 77 | -------------------------------------------------------------------------------- /e2e-tests/internal/test_internal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | export KUBECONFIG=/.kube/config 5 | 6 | # get server ip. This used to be hardcoded, but my local docker has a different 7 | # value for this than CircleCI, so ... let's make it check. 8 | ip_prefix=$(cat /etc/hosts | grep -o 172.[0-9]*.0.) 9 | ip_suffix=0 10 | port=6443 11 | set +e 12 | until ( nc -z "${ip_prefix}${ip_suffix}" $port ); do 13 | echo "tried: ${ip_prefix}${ip_suffix}" 14 | ((ip_suffix++)) 15 | if [[ "$ip_suffix" -ge 255 ]]; then 16 | echo "Could not find control plane in ${ip_prefix}0-254." 17 | exit 1 18 | fi 19 | done 20 | set -e 21 | echo "Control plane ip: ${ip_prefix}${ip_suffix}." 22 | 23 | kubectl config set-cluster kind-kind --server=https://${ip_prefix}${ip_suffix}:$port 24 | kubectl config set-context kind-kind 25 | # Configure the agent, a basic nginx service, and a mock Honeycomb API host for 26 | # the agent to write to 27 | kubectl create secret generic -n kube-system honeycomb-writekey --from-literal=key=testkey 28 | kubectl apply -f /testspec.yaml 29 | 30 | kubectl wait --for=condition=available --timeout=30s deployment/nginx-deployment 31 | kubectl wait --for=condition=available --timeout=30s deployment/apihost-deployment 32 | kubectl port-forward svc/nginx-service 9111:80 & 33 | kubectl port-forward svc/apihost-service 9112:5000 & 34 | 35 | sleep 15 36 | 37 | NGINX_URL=localhost:9111 38 | API_URL=localhost:9112 39 | 40 | # Make a request to NGINX, check that the agent sends an event to the mock API 41 | curl $NGINX_URL 42 | 43 | sleep 1 44 | 45 | ret=$(curl $API_URL) 46 | echo "Events received by mock API host:" 47 | echo $ret 48 | count=$(echo $ret | jq ".kubernetestest | length") 49 | if [ $count -ne 1 ]; then 50 | echo "Didn't receive expected number of events!" 51 | echo "agent logs:" 52 | kubectl logs -n kube-system -l app=honeycomb-agent 53 | exit 1 54 | fi 55 | kubectl delete pod,svc --all 56 | -------------------------------------------------------------------------------- /parsers/redis.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "time" 7 | ) 8 | 9 | // The only reference I can find for this format is: 10 | // http://build47.com/redis-log-format-levels/ 11 | // This formatter targets Redis 3+, which was released in January 2016 12 | const redisLineFormat = `(?P[0-9]+):(?P[XCSM])\s(?P[0-9]{2}\s[A-Za-z]*\s[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+)\s(?P[.\-*#])\s(?P.*)` 13 | const redisDateFormat = "02 Jan 15:04:05.000" 14 | 15 | var redisRoles = map[string]string{ 16 | "X": "sentinel", 17 | "C": "child", 18 | "S": "slave", 19 | "M": "master", 20 | } 21 | 22 | var redisLevels = map[string]string{ 23 | ".": "debug", 24 | "-": "verbose", 25 | "*": "notice", 26 | "#": "warning", 27 | } 28 | 29 | type RedisParser struct { 30 | re *extRegexp 31 | } 32 | 33 | func (p *RedisParser) Parse(line string) (map[string]interface{}, error) { 34 | _, captures := p.re.FindStringSubmatchMap(line) 35 | 36 | if captures == nil { 37 | return nil, fmt.Errorf("Couldn't parse line as Redis line: %s", line) 38 | } 39 | 40 | ret := make(map[string]interface{}, 0) 41 | if level, ok := redisLevels[captures["level"]]; ok { 42 | ret["level"] = level 43 | } else { 44 | ret["level"] = captures["level"] 45 | } 46 | if role, ok := redisRoles[captures["role"]]; ok { 47 | ret["role"] = role 48 | } else { 49 | ret["role"] = captures["role"] 50 | } 51 | ret["pid"] = captures["pid"] 52 | ret["message"] = captures["message"] 53 | 54 | ts, err := time.Parse(redisDateFormat, captures["timestamp"]) 55 | if err == nil { 56 | ts = ts.AddDate(time.Now().Year(), 0, 0) 57 | ret["redis_timestamp"] = ts 58 | } 59 | 60 | return ret, nil 61 | } 62 | 63 | type RedisParserFactory struct{} 64 | 65 | func (pf *RedisParserFactory) Init(options map[string]interface{}) error { return nil } 66 | 67 | func (pf *RedisParserFactory) New() Parser { 68 | return &RedisParser{ 69 | re: &extRegexp{regexp.MustCompile(redisLineFormat)}, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /LICENSES/gopkg.in/yaml.v3/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | This project is covered by two different licenses: MIT and Apache. 3 | 4 | #### MIT License #### 5 | 6 | The following files were ported to Go from C files of libyaml, and thus 7 | are still covered by their original MIT license, with the additional 8 | copyright staring in 2011 when the project was ported over: 9 | 10 | apic.go emitterc.go parserc.go readerc.go scannerc.go 11 | writerc.go yamlh.go yamlprivateh.go 12 | 13 | Copyright (c) 2006-2010 Kirill Simonov 14 | Copyright (c) 2006-2011 Kirill Simonov 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy of 17 | this software and associated documentation files (the "Software"), to deal in 18 | the Software without restriction, including without limitation the rights to 19 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 20 | of the Software, and to permit persons to whom the Software is furnished to do 21 | so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | 34 | ### Apache License ### 35 | 36 | All the remaining project files are covered by the Apache license: 37 | 38 | Copyright (c) 2011-2019 Canonical Ltd 39 | 40 | Licensed under the Apache License, Version 2.0 (the "License"); 41 | you may not use this file except in compliance with the License. 42 | You may obtain a copy of the License at 43 | 44 | http://www.apache.org/licenses/LICENSE-2.0 45 | 46 | Unless required by applicable law or agreed to in writing, software 47 | distributed under the License is distributed on an "AS IS" BASIS, 48 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 49 | See the License for the specific language governing permissions and 50 | limitations under the License. 51 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline processing engine, see https://circleci.com/docs/2.0/configuration-reference/ 2 | version: 2.1 3 | 4 | filters_always: &filters_always 5 | filters: 6 | tags: 7 | only: /.*/ 8 | 9 | filters_publish: &filters_publish 10 | filters: 11 | tags: 12 | only: /^v[0-9].*/ 13 | branches: 14 | ignore: /.*/ 15 | 16 | executors: 17 | docker-go: 18 | docker: 19 | - image: cimg/go:1.24 20 | 21 | jobs: 22 | build_agent: 23 | executor: docker-go 24 | steps: 25 | - setup_remote_docker 26 | - checkout 27 | - run: go install github.com/google/ko@latest 28 | - run: echo "VERSION=$(cat version.txt | tr -d '\n')" >> $BASH_ENV 29 | - run: make verify-licenses 30 | - run: ./build/build.sh 31 | - run: ./build/test.sh . 32 | - run: e2e-tests/test.sh 33 | 34 | publish_dockerhub: 35 | executor: docker-go 36 | steps: 37 | - setup_remote_docker 38 | - checkout 39 | - run: go install github.com/google/ko@latest 40 | - run: echo "VERSION=$(cat version.txt | tr -d '\n')" >> $BASH_ENV 41 | - run: 42 | name: "publish image to Docker Hub" 43 | environment: 44 | KO_DOCKER_REPO: honeycombio 45 | command: | 46 | echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin; 47 | ./build/build.sh 48 | 49 | publish_github: 50 | docker: 51 | - image: cibuilds/github:0.13.0 52 | steps: 53 | - attach_workspace: 54 | at: ~/ 55 | - run: 56 | name: "Publish Release on GitHub" 57 | command: | 58 | echo "about to publish to tag ${CIRCLE_TAG}" 59 | ghr --draft -n ${CIRCLE_TAG} -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${CIRCLE_TAG} 60 | 61 | workflows: 62 | version: 2 63 | build: 64 | jobs: 65 | - build_agent: 66 | <<: *filters_always 67 | - publish_dockerhub: 68 | <<: *filters_publish 69 | context: Honeycomb Secrets for Public Repos 70 | requires: 71 | - build_agent 72 | - publish_github: 73 | <<: *filters_publish 74 | context: Honeycomb Secrets for Public Repos 75 | requires: 76 | - build_agent 77 | -------------------------------------------------------------------------------- /interval/interval_runner.go: -------------------------------------------------------------------------------- 1 | // Originally inspired by OpenTelemetry Collector kubeletstats receiver 2 | // https://github.com/open-telemetry/opentelemetry-collector 3 | 4 | package interval 5 | 6 | import ( 7 | "github.com/sirupsen/logrus" 8 | "time" 9 | ) 10 | 11 | // Runner takes a list of `Runnable`s, calls Setup() on each of them and then 12 | // calls Run() on each of them, using a Ticker, sequentially and within the same 13 | // goroutine. Call Stop() to turn off the Ticker. 14 | type Runner struct { 15 | name string 16 | runnables []Runnable 17 | ticker *time.Ticker 18 | logger *logrus.Entry 19 | } 20 | 21 | // NewRunner creates a new interval runner. Pass in a duration (time between 22 | // calls) and one or more Runnables to be run on the defined interval. 23 | func NewRunner(name string, interval time.Duration, runnables ...Runnable) *Runner { 24 | return &Runner{ 25 | name: name, 26 | runnables: runnables, 27 | ticker: time.NewTicker(interval), 28 | logger: logrus.WithFields(logrus.Fields{"runner.name": name}), 29 | } 30 | } 31 | 32 | // Runnable must be implemented by types passed into the Runner constructor. 33 | type Runnable interface { 34 | // called once at Start() time 35 | Setup() error 36 | // called on the interval defined by the 37 | // duration passed into NewRunner 38 | Run() error 39 | } 40 | 41 | // Start kicks off this Runner. Calls Setup() and Run() on the passed-in 42 | // Runnables. 43 | func (r *Runner) Start() error { 44 | r.logger.Debug("Starting Runner...") 45 | err := r.setup() 46 | if err != nil { 47 | r.logger.WithError(err).Error("Failed to setup Runner") 48 | return err 49 | } 50 | err = r.run() 51 | if err != nil { 52 | r.logger.WithError(err).Error("Failed to run Runner") 53 | return err 54 | } 55 | r.logger.Debug("Runner Started") 56 | return nil 57 | } 58 | 59 | func (r *Runner) setup() error { 60 | for _, runnable := range r.runnables { 61 | err := runnable.Setup() 62 | if err != nil { 63 | return err 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | func (r *Runner) run() error { 70 | for range r.ticker.C { 71 | for _, runnable := range r.runnables { 72 | err := runnable.Run() 73 | if err != nil { 74 | return err 75 | } 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | // Stop turns off this Runner's ticker. 82 | func (r *Runner) Stop() { 83 | r.ticker.Stop() 84 | } 85 | -------------------------------------------------------------------------------- /.github/workflows/validate-pr-title.yml: -------------------------------------------------------------------------------- 1 | name: "Validate PR Title" 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | jobs: 11 | main: 12 | name: Validate PR title 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: amannn/action-semantic-pull-request@v5 16 | id: lint_pr_title 17 | name: "🤖 Check PR title follows conventional commit spec" 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | # Have to specify all types because `maint` and `rel` aren't defaults 22 | types: | 23 | maint 24 | rel 25 | fix 26 | feat 27 | chore 28 | ci 29 | docs 30 | style 31 | refactor 32 | perf 33 | test 34 | ignoreLabels: | 35 | "type: dependencies" 36 | # When the previous steps fails, the workflow would stop. By adding this 37 | # condition you can continue the execution with the populated error message. 38 | - if: always() && (steps.lint_pr_title.outputs.error_message != null) 39 | name: "📝 Add PR comment about using conventional commit spec" 40 | uses: marocchino/sticky-pull-request-comment@v2 41 | with: 42 | header: pr-title-lint-error 43 | message: | 44 | Thank you for contributing to the project! 🎉 45 | 46 | We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted. 47 | 48 | Make sure to prepend with `feat:`, `fix:`, or another option in the list below. 49 | 50 | Once you update the title, this workflow will re-run automatically and validate the updated title. 51 | 52 | Details: 53 | 54 | ``` 55 | ${{ steps.lint_pr_title.outputs.error_message }} 56 | ``` 57 | 58 | # Delete a previous comment when the issue has been resolved 59 | - if: ${{ steps.lint_pr_title.outputs.error_message == null }} 60 | name: "❌ Delete PR comment after title has been updated" 61 | uses: marocchino/sticky-pull-request-comment@v2 62 | with: 63 | header: pr-title-lint-error 64 | delete: true 65 | -------------------------------------------------------------------------------- /parsers/keyval.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/kr/logfmt" 8 | ) 9 | 10 | type KeyvalParserFactory struct { 11 | prefixExpr string 12 | prefixRegex *extRegexp 13 | timeFieldName string 14 | timeFieldFormat string 15 | } 16 | 17 | func (pf *KeyvalParserFactory) Init(options map[string]interface{}) error { 18 | if prefixExpr, ok := options["prefixRegex"]; ok { 19 | typedPrefixExpr, ok := prefixExpr.(string) 20 | if !ok { 21 | return fmt.Errorf("Invalid type for prefixRegex option (expected string)") 22 | } 23 | if typedPrefixExpr != "" { 24 | re, err := newExtRegexp("^" + typedPrefixExpr) // only match start of line 25 | if err != nil { 26 | return fmt.Errorf("Invalid regex value for prefixRegex option: `%s`", typedPrefixExpr) 27 | } 28 | pf.prefixRegex = re 29 | pf.prefixExpr = typedPrefixExpr 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | func (pf *KeyvalParserFactory) New() Parser { 36 | return &KeyvalParser{ 37 | prefixExpr: pf.prefixExpr, 38 | prefixRegex: pf.prefixRegex, 39 | } 40 | } 41 | 42 | type KeyvalParser struct { 43 | prefixExpr string 44 | prefixRegex *extRegexp 45 | } 46 | 47 | func (p *KeyvalParser) Parse(line string) (map[string]interface{}, error) { 48 | ret := make(map[string]interface{}) 49 | var prefixLength int 50 | if p.prefixRegex != nil { 51 | prefixMatch, prefixCaptures := p.prefixRegex.FindStringSubmatchMap(line) 52 | if prefixMatch == "" { 53 | return nil, fmt.Errorf("Couldn't match line prefix %s for line %s", p.prefixExpr, line) 54 | } 55 | for k, v := range prefixCaptures { 56 | ret[k] = v 57 | } 58 | prefixLength = len(prefixMatch) 59 | } 60 | 61 | err := logfmt.Unmarshal([]byte(line[prefixLength:]), getHandler(ret)) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return ret, nil 66 | } 67 | 68 | func getHandler(ret map[string]interface{}) logfmt.HandlerFunc { 69 | return func(key, val []byte) error { 70 | keyStr := string(key) 71 | valStr := string(val) 72 | if b, err := strconv.ParseBool(valStr); err == nil { 73 | ret[keyStr] = b 74 | return nil 75 | } 76 | if i, err := strconv.Atoi(valStr); err == nil { 77 | ret[keyStr] = i 78 | return nil 79 | } 80 | if f, err := strconv.ParseFloat(valStr, 64); err == nil { 81 | ret[keyStr] = f 82 | return nil 83 | } 84 | ret[keyStr] = valStr 85 | return nil 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /processors/processors.go: -------------------------------------------------------------------------------- 1 | // Package processors contains support for mutating event data after it's been 2 | // parsed out of an event line. 3 | package processors 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 10 | ) 11 | 12 | // Processor is the interface that processors implement. The Init() method is 13 | // called to initialize the processor. Process() mutates event data in-place. 14 | // Processors should return `true` if processing and sending should continue, 15 | // and `false` if not. This is primarily relevant for sampling, and other 16 | // processors should always return true. 17 | type Processor interface { 18 | Process(*event.Event) bool 19 | Init(options map[string]interface{}) error 20 | } 21 | 22 | // NewProcessorFromConfig takes a configuration map that's been unmarshalled 23 | // out of YAML, and tries to instantiate a corresponding processor. 24 | // The syntax for processor configuration is: 25 | // processors: 26 | // - request_shape: 27 | // field: request 28 | // prefix: shaped 29 | // or equivalently: 30 | // {"processors": [{"request_shape": {"field": "request", "prefix": "shaped"}}]} 31 | // So NewProcessorFromConfig expects to get a map with one key (the name of the 32 | // processor). 33 | func NewProcessorFromConfig(config map[string]map[string]interface{}) (Processor, error) { 34 | if len(config) != 1 { 35 | // TODO: better error 36 | return nil, fmt.Errorf("Invalid processor configuration") 37 | } 38 | for name, options := range config { 39 | return NewProcessor(name, options) 40 | } 41 | 42 | return nil, errors.New("No processor found") 43 | } 44 | 45 | func NewProcessor(name string, options map[string]interface{}) (Processor, error) { 46 | var p Processor 47 | switch name { 48 | case "route_event": 49 | p = &EventRouter{} 50 | case "request_shape": 51 | p = &RequestShaper{} 52 | case "drop_field": 53 | p = &FieldDropper{} 54 | case "scrub_field": 55 | p = &FieldScrubber{} 56 | case "drop_event": 57 | p = &EventDropper{} 58 | case "keep_event": 59 | p = &EventKeeper{} 60 | case "sample": 61 | p = &Sampler{} 62 | case "timefield": 63 | p = &TimeFieldExtractor{} 64 | case "rename_field": 65 | p = &FieldRenamer{} 66 | case "additional_fields": 67 | p = &AdditionalFieldsProcessor{} 68 | default: 69 | return nil, fmt.Errorf("Unknown processor type %s", name) 70 | } 71 | err := p.Init(options) 72 | return p, err 73 | } 74 | -------------------------------------------------------------------------------- /processors/kubernetes.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 7 | "github.com/honeycombio/honeycomb-kubernetes-agent/k8sagent" 8 | api "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/types" 10 | ) 11 | 12 | type KubernetesMetadataProcessor struct { 13 | PodGetter k8sagent.PodWatcher 14 | UID types.UID 15 | lastPodData *api.Pod 16 | } 17 | 18 | func (k *KubernetesMetadataProcessor) Init(options map[string]interface{}) error { 19 | return nil 20 | } 21 | 22 | func (k *KubernetesMetadataProcessor) Process(ev *event.Event) bool { 23 | pod, ok := k.PodGetter.Get(k.UID) 24 | if ok { 25 | k.lastPodData = pod 26 | } else if k.lastPodData != nil { 27 | pod = k.lastPodData 28 | } 29 | if pod != nil { 30 | containerName := getContainerNameFromPath(ev.Path) 31 | metadata := extractMetadataFromPod(pod, containerName) 32 | for k, v := range metadata { 33 | ev.Data["kubernetes."+k] = v 34 | } 35 | } 36 | return true 37 | } 38 | 39 | func extractMetadataFromPod(pod *api.Pod, containerName string) map[string]interface{} { 40 | ret := make(map[string]interface{}) 41 | ret["pod.labels"] = pod.Labels 42 | ret["pod.name"] = pod.Name 43 | ret["pod.namespace"] = pod.Namespace 44 | ret["pod.resourceVersion"] = pod.ResourceVersion 45 | ret["pod.UID"] = string(pod.UID) 46 | ret["pod.nodeName"] = pod.Spec.NodeName 47 | ret["pod.nodeSelector"] = pod.Spec.NodeSelector 48 | ret["pod.serviceAccountName"] = pod.Spec.ServiceAccountName 49 | ret["pod.subdomain"] = pod.Spec.Subdomain 50 | ret["pod.annotations"] = pod.Annotations 51 | 52 | for _, container := range pod.Spec.Containers { 53 | if container.Name == containerName { 54 | ret["container.args"] = container.Args 55 | ret["container.command"] = container.Command 56 | ret["container.name"] = container.Name 57 | ret["container.env"] = container.Env 58 | ret["container.image"] = container.Image 59 | ret["container.ports"] = container.Ports 60 | ret["container.VolumeMounts"] = container.VolumeMounts 61 | ret["container.workingDir"] = container.WorkingDir 62 | ret["container.resources"] = container.Resources 63 | } 64 | } 65 | return ret 66 | } 67 | 68 | func getContainerNameFromPath(filePath string) string { 69 | r := regexp.MustCompile("/var/log/pods/(.*)/(?P.*)/[0-9]+.log") 70 | match := r.FindStringSubmatch(filePath) 71 | if match == nil { 72 | return "" 73 | } 74 | if len(match) < 3 { 75 | return "" 76 | } 77 | return match[2] 78 | } 79 | -------------------------------------------------------------------------------- /metrics/conventions.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | const ( 4 | LabelNodeName = PrefixNode + "name" 5 | LabelNamespaceName = PrefixNamespace + "name" 6 | LabelPodUid = PrefixPod + "uid" 7 | LabelPodName = PrefixPod + "name" 8 | LabelContainerName = PrefixContainer + "name" 9 | LabelContainerId = PrefixContainer + "id" 10 | LabelVolumeName = PrefixVolume + "name" 11 | 12 | StatusMessage = PrefixStatus + "message" 13 | StatusReason = PrefixStatus + "reason" 14 | StatusReady = PrefixStatus + "ready" 15 | StatusState = PrefixStatus + "state" 16 | StatusExitCode = PrefixStatus + "exitcode" 17 | StatusPhase = PrefixStatus + "phase" 18 | StatusRestart = PrefixStatus + "restart" 19 | StatusRestartCount = PrefixStatus + "restart_count" 20 | StatusRestartDelta = PrefixStatus + "restart_delta" 21 | 22 | MeasureUptime = "uptime" 23 | MeasureCpuUsage = "cpu.usage" 24 | MeasureCpuUtilization = "cpu.utilization" 25 | MeasureMemoryAvailable = "memory.available" 26 | MeasureMemoryUsage = "memory.usage" 27 | MeasureMemoryUtilization = "memory.utilization" 28 | MeasureMemoryRSS = "memory.rss" 29 | MeasureMemoryWorkingSet = "memory.working_set" 30 | MeasureMemoryPageFaults = "memory.page_faults" 31 | MeasureMemoryMajorPageFaults = "memory.major_page_faults" 32 | MeasureFilesystemAvailable = "filesystem.available" 33 | MeasureFilesystemCapacity = "filesystem.capacity" 34 | MeasureFilesystemUsage = "filesystem.usage" 35 | MeasureNetworkBytesReceive = "network.bytes.receive" 36 | MeasureNetworkBytesSend = "network.bytes.send" 37 | MeasureNetworkErrorsReceive = "network.errors.receive" 38 | MeasureNetworkErrorsSend = "network.errors.send" 39 | MeasureVolumeAvailable = "volume.available" 40 | MeasureVolumeCapacity = "volume.capacity" 41 | MeasureVolumeUsed = "volume.used" 42 | MeasureVolumeInodesTotal = "volume.inodes.total" 43 | MeasureVolumeInodesFree = "volume.inodes.free" 44 | MeasureVolumeInodesUsed = "volume.inodes.used" 45 | 46 | PrefixK8s = "k8s." 47 | PrefixMetrics = "metrics." 48 | PrefixStatus = "status." 49 | PrefixLabel = "label." 50 | PrefixCluster = PrefixK8s + "cluster." 51 | PrefixNode = PrefixK8s + "node." 52 | PrefixNamespace = PrefixK8s + "namespace." 53 | PrefixPod = PrefixK8s + "pod." 54 | PrefixContainer = PrefixK8s + "container." 55 | PrefixVolume = PrefixK8s + "volume." 56 | 57 | MetricSourceName = "source" 58 | MetricSourceType = "source.type" 59 | KubernetesResourceType = PrefixK8s + "resource.type" 60 | ) 61 | -------------------------------------------------------------------------------- /processors/urlshaper.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 9 | "github.com/honeycombio/urlshaper" 10 | "github.com/mitchellh/mapstructure" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type RequestShaper struct { 15 | config *requestShaperConfig 16 | shaper *urlshaper.Parser 17 | } 18 | 19 | type requestShaperConfig struct { 20 | Field string 21 | Prefix string 22 | Patterns []string 23 | QueryKeys []string `yaml:"queryKeys"` 24 | } 25 | 26 | func (r *RequestShaper) Init(options map[string]interface{}) error { 27 | config := &requestShaperConfig{} 28 | err := mapstructure.Decode(options, config) 29 | if err != nil { 30 | return err 31 | } 32 | r.config = config 33 | r.shaper = &urlshaper.Parser{} 34 | for _, patternString := range config.Patterns { 35 | pat := urlshaper.Pattern{Pat: patternString} 36 | if err := pat.Compile(); err != nil { 37 | return fmt.Errorf("Invalid pattern %s", patternString) 38 | } 39 | r.shaper.Patterns = append(r.shaper.Patterns, 40 | &pat) 41 | } 42 | 43 | return nil 44 | } 45 | 46 | // This is mostly borrowed from github.com/honeycombio/honeytail 47 | func (r *RequestShaper) Process(ev *event.Event) bool { 48 | data := ev.Data 49 | val, ok := data[r.config.Field] 50 | if !ok { 51 | return true 52 | } 53 | valString, ok := val.(string) 54 | if !ok { 55 | logrus.WithFields(logrus.Fields{ 56 | "key": r.config.Field, 57 | "value": val}). 58 | Debug("Not shaping field of non-string type") 59 | } 60 | 61 | result := make(map[string]interface{}) 62 | parts := strings.Split(valString, " ") 63 | var path string 64 | if len(parts) == 3 { 65 | result["method"] = parts[0] 66 | result["protocol_version"] = parts[2] 67 | path = parts[1] 68 | } else { 69 | path = parts[0] 70 | } 71 | 72 | shapedPath, err := r.shaper.Parse(path) 73 | if err != nil { 74 | return true 75 | } 76 | 77 | result["uri"] = shapedPath.URI 78 | result["path"] = shapedPath.Path 79 | result["query"] = shapedPath.Query 80 | for k, v := range shapedPath.QueryFields { 81 | for _, whitelistedKey := range r.config.QueryKeys { 82 | if whitelistedKey == k { 83 | sort.Strings(v) 84 | result["query_"+k] = strings.Join(v, ", ") 85 | } 86 | } 87 | } 88 | for k, v := range shapedPath.PathFields { 89 | result["path_"+k] = v[0] 90 | } 91 | result["shape"] = shapedPath.Shape 92 | result["pathshape"] = shapedPath.PathShape 93 | result["queryshape"] = shapedPath.QueryShape 94 | 95 | prefix := r.config.Prefix + r.config.Field 96 | for k, v := range result { 97 | data[prefix+"_"+k] = v 98 | } 99 | return true 100 | } 101 | -------------------------------------------------------------------------------- /processors/rename_field_test.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRenameField(t *testing.T) { 11 | // ensures FieldRenamer implements the Processor interface 12 | var processor Processor 13 | processor = &FieldRenamer{} 14 | 15 | err := processor.Init(map[string]interface{}{ 16 | "original": "time", 17 | "new": "timestamp", 18 | }) 19 | assert.Equal(t, nil, err, "init should not error") 20 | 21 | e := &event.Event{ 22 | Data: map[string]interface{}{ 23 | "time": "tomorrow", 24 | "msg": "leave me be", 25 | }, 26 | } 27 | cont := processor.Process(e) 28 | assert.Equal(t, true, cont, "Process should return true, to signal continued processing") 29 | assert.Equal(t, nil, e.Data["time"], "time field should be removed") 30 | assert.Equal(t, "tomorrow", e.Data["timestamp"], "timestamp field should be present") 31 | assert.Equal(t, "leave me be", e.Data["msg"], "msg field should be unchanged") 32 | 33 | // we should not crash if no data is present 34 | cont = processor.Process(&event.Event{Data: map[string]interface{}{}}) 35 | assert.Equal(t, true, cont, "Process should return true, to signal continued processing") 36 | } 37 | 38 | func TestRenameFieldOverwritesExisting(t *testing.T) { 39 | // ensures FieldRenamer implements the Processor interface 40 | var processor Processor 41 | processor = &FieldRenamer{} 42 | 43 | err := processor.Init(map[string]interface{}{ 44 | "original": "time", 45 | "new": "timestamp", 46 | }) 47 | assert.Equal(t, nil, err, "init should not error") 48 | 49 | e := &event.Event{ 50 | Data: map[string]interface{}{ 51 | "time": "tomorrow", 52 | "timestamp": "yesterday", 53 | }, 54 | } 55 | cont := processor.Process(e) 56 | assert.Equal(t, true, cont, "Process should return true, to signal continued processing") 57 | assert.Equal(t, nil, e.Data["time"], "time field should be removed") 58 | assert.Equal(t, "tomorrow", e.Data["timestamp"], "timestamp field should have new value") 59 | } 60 | 61 | func TestRenameFieldInvalidConfig(t *testing.T) { 62 | processor := &FieldRenamer{} 63 | err := processor.Init(map[string]interface{}{ 64 | "original": "time", 65 | "new": "time", 66 | }) 67 | assert.Equal(t, ErrFieldOptionsMatch, err, "matching new and original field names should return an error") 68 | err = processor.Init(map[string]interface{}{ 69 | "original": "time", 70 | }) 71 | assert.Equal(t, ErrFieldOptionUnspecified, err, "an error should be returned if new is not specified") 72 | err = processor.Init(map[string]interface{}{ 73 | "new": "time", 74 | }) 75 | assert.Equal(t, ErrFieldOptionUnspecified, err, "an error shuould be returned if original is not specified") 76 | } 77 | -------------------------------------------------------------------------------- /metrics/ttlcache.go: -------------------------------------------------------------------------------- 1 | // TTL Cache Inspired by https://github.com/wunderlist/ttlcache 2 | // Modifications: 3 | // - metrics.CounterValue for Item data 4 | // - removes ability to update expiration on get 5 | // - reduces locks 6 | 7 | package metrics 8 | 9 | import ( 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // Item represents a record in the cache map 15 | type Item struct { 16 | sync.RWMutex 17 | data *CounterValue 18 | expires *time.Time 19 | } 20 | 21 | func (item *Item) touch(duration time.Duration) { 22 | item.Lock() 23 | expiration := time.Now().Add(duration) 24 | item.expires = &expiration 25 | item.Unlock() 26 | } 27 | 28 | func (item *Item) expired() bool { 29 | var value bool 30 | item.RLock() 31 | if item.expires == nil { 32 | value = true 33 | } else { 34 | value = item.expires.Before(time.Now()) 35 | } 36 | item.RUnlock() 37 | return value 38 | } 39 | 40 | // Cache is a synchronised map of items that auto-expire once stale 41 | type Cache struct { 42 | mutex sync.RWMutex 43 | ttl time.Duration 44 | items map[string]*Item 45 | } 46 | 47 | // Set is a thread-safe way to add new items to the map 48 | func (cache *Cache) Set(key string, data *CounterValue) { 49 | expiration := time.Now().Add(cache.ttl) 50 | item := &Item{ 51 | data: data, 52 | expires: &expiration, 53 | } 54 | cache.mutex.Lock() 55 | cache.items[key] = item 56 | cache.mutex.Unlock() 57 | } 58 | 59 | // Get is a thread-safe way to lookup items 60 | func (cache *Cache) Get(key string) (*CounterValue, bool) { 61 | item, exists := cache.items[key] 62 | if !exists || item.expired() { 63 | return nil, false 64 | } else { 65 | return item.data, true 66 | } 67 | } 68 | 69 | // Count returns the number of items in the cache 70 | // (helpful for tracking memory leaks) 71 | func (cache *Cache) Count() int { 72 | cache.mutex.RLock() 73 | count := len(cache.items) 74 | cache.mutex.RUnlock() 75 | return count 76 | } 77 | 78 | func (cache *Cache) cleanup() { 79 | cache.mutex.Lock() 80 | for key, item := range cache.items { 81 | if item.expired() { 82 | delete(cache.items, key) 83 | } 84 | } 85 | cache.mutex.Unlock() 86 | } 87 | 88 | func (cache *Cache) startCleanupTimer() { 89 | duration := cache.ttl 90 | if duration < time.Second { 91 | duration = time.Second 92 | } 93 | ticker := time.Tick(duration) 94 | go (func() { 95 | for { 96 | select { 97 | case <-ticker: 98 | cache.cleanup() 99 | } 100 | } 101 | })() 102 | } 103 | 104 | // NewCache is a helper to create instance of the Cache struct 105 | func NewCache(duration time.Duration) *Cache { 106 | cache := &Cache{ 107 | ttl: duration, 108 | items: map[string]*Item{}, 109 | } 110 | cache.startCleanupTimer() 111 | return cache 112 | } 113 | -------------------------------------------------------------------------------- /parsers/glog.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | // glog is a logging format from Google that's used by Kubernetes components 4 | // (API server, etc.) 5 | 6 | import ( 7 | "fmt" 8 | "regexp" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | // The only reference I can find for this format is: 14 | // https://github.com/google/glog/blob/master/src/logging.cc#L1077 15 | const lineformat = `(?P[IWEF])(?P[0-9]{2})(?P[0-9]{2}) (?P[0-9]{2}):(?P[0-9]{2}):(?P[0-9]{2})\.(?P[0-9]*)\s+(?P[0-9]*) (?P[^:]*):(?P[0-9]*)\] (?P.*)` 16 | 17 | var levels = map[string]string{ 18 | "I": "info", 19 | "W": "warning", 20 | "E": "error", 21 | "F": "fatal", 22 | } 23 | 24 | type GlogParser struct { 25 | re *extRegexp 26 | inFlight map[string]interface{} 27 | } 28 | 29 | // TODO: we should support concatenating multiline log statements, because kube 30 | // api server logs, for example, contain lines such as the following (ugh). 31 | // I0720 00:23:31.949027 5 trace.go:61] Trace "GuaranteedUpdate etcd3: *api.Node" (started 2017-07-20 00:23:30.517702742 +0000 UTC): 32 | // [68.006µs] [68.006µs] initial value restored 33 | // [1.03334ms] [965.334µs] Transaction prepared 34 | // [1.431248874s] [1.430215534s] Transaction committed 35 | // "GuaranteedUpdate etcd3: *api.Node" [1.431304615s] [55.741µs] END 36 | func (p *GlogParser) Parse(line string) (map[string]interface{}, error) { 37 | _, captures := p.re.FindStringSubmatchMap(line) 38 | 39 | if captures == nil { 40 | return nil, fmt.Errorf("Couldn't parse line as glog line: %s", line) 41 | } 42 | 43 | ret := make(map[string]interface{}, 0) 44 | if level, ok := levels[captures["level"]]; ok { 45 | ret["level"] = level 46 | } else { 47 | ret["level"] = captures["level"] 48 | } 49 | ret["threadid"] = captures["threadid"] 50 | ret["filename"] = captures["filename"] 51 | ret["lineno"] = captures["lineno"] 52 | ret["message"] = captures["message"] 53 | 54 | ts, err := parseGlogTimestamp( 55 | captures["month"], captures["day"], captures["hour"], captures["minute"], captures["second"], captures["microsecond"]) 56 | if err == nil { 57 | ret["glog_timestamp"] = ts 58 | } 59 | 60 | return ret, nil 61 | } 62 | 63 | type GlogParserFactory struct{} 64 | 65 | func (pf *GlogParserFactory) Init(options map[string]interface{}) error { return nil } 66 | 67 | func (pf *GlogParserFactory) New() Parser { 68 | return &GlogParser{ 69 | re: &extRegexp{regexp.MustCompile(lineformat)}, 70 | } 71 | } 72 | 73 | func parseGlogTimestamp(month string, day string, hour string, minute string, second string, microsecond string) (time.Time, error) { 74 | year := time.Now().Year() 75 | var err error 76 | atoi := func(raw string) int { 77 | v, newErr := strconv.Atoi(raw) 78 | err = newErr 79 | return v 80 | } 81 | return time.Date(year, time.Month(atoi(month)), atoi(day), atoi(hour), atoi(minute), atoi(second), atoi(microsecond)*1e3, time.UTC), err 82 | } 83 | -------------------------------------------------------------------------------- /processors/sample.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/rand" 7 | "strconv" 8 | "strings" 9 | 10 | dynsampler "github.com/honeycombio/dynsampler-go" 11 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 12 | "github.com/mitchellh/mapstructure" 13 | ) 14 | 15 | type SampleType string 16 | 17 | const ( 18 | SampleTypeStatic SampleType = "static" 19 | SampleTypeDynamic SampleType = "dynamic" 20 | ) 21 | 22 | type Sampler struct { 23 | config *samplerConfig 24 | dynsampler dynsampler.Sampler 25 | } 26 | 27 | type samplerConfig struct { 28 | Type SampleType 29 | Rate uint 30 | Keys []string 31 | WindowSize int 32 | MinEventsPerSec int 33 | } 34 | 35 | func (s *Sampler) Init(options map[string]interface{}) error { 36 | config := &samplerConfig{} 37 | err := mapstructure.Decode(options, config) 38 | if err != nil { 39 | return err 40 | } 41 | if config.Type == "" { 42 | // Default to static if not otherwise specified 43 | config.Type = SampleTypeStatic 44 | } 45 | if config.Type != SampleTypeStatic && config.Type != SampleTypeDynamic { 46 | return errors.New("sample type must be either 'static' or 'dynamic'") 47 | } 48 | if config.WindowSize == 0 { 49 | // Default to 30 seconds if not otherwise specified 50 | config.WindowSize = 30 51 | } 52 | // MinEventsPerSec is defaulted to 50 by the sampler itself, if the value 53 | // specified in our config is 0 or missing. 54 | 55 | s.config = config 56 | 57 | if s.config.Type == SampleTypeDynamic { 58 | s.dynsampler = &dynsampler.AvgSampleWithMin{ 59 | GoalSampleRate: int(config.Rate), 60 | ClearFrequencySec: config.WindowSize, 61 | MinEventsPerSec: config.MinEventsPerSec, 62 | } 63 | if err := s.dynsampler.Start(); err != nil { 64 | return fmt.Errorf("error starting dynamic sampler: %v", err) 65 | } 66 | } 67 | return nil 68 | } 69 | 70 | func (s *Sampler) Process(ev *event.Event) bool { 71 | var rate uint 72 | if s.config.Type == SampleTypeStatic { 73 | rate = s.config.Rate 74 | } else { 75 | key := makeDynSampleKey(ev, s.config.Keys) 76 | rate = uint(s.dynsampler.GetSampleRate(key)) 77 | } 78 | ev.SampleRate = rate 79 | return !shouldDrop(rate) 80 | 81 | } 82 | 83 | func shouldDrop(rate uint) bool { 84 | return rand.Intn(int(rate)) != 0 85 | } 86 | 87 | // From honeytail 88 | func makeDynSampleKey(ev *event.Event, keys []string) string { 89 | key := make([]string, len(keys)) 90 | for i, field := range keys { 91 | if val, ok := ev.Data[field]; ok { 92 | switch val := val.(type) { 93 | case bool: 94 | key[i] = strconv.FormatBool(val) 95 | case int: 96 | key[i] = strconv.Itoa(val) 97 | case int64: 98 | key[i] = strconv.FormatInt(val, 10) 99 | case float64: 100 | key[i] = strconv.FormatFloat(val, 'E', -1, 64) 101 | case string: 102 | key[i] = val 103 | default: 104 | key[i] = "" // skip it 105 | } 106 | } 107 | } 108 | return strings.Join(key, "_") 109 | } 110 | -------------------------------------------------------------------------------- /transmission/ringbuffer.go: -------------------------------------------------------------------------------- 1 | package transmission 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | type BufferEvent struct { 12 | *event.Event 13 | expires *time.Time 14 | } 15 | 16 | func (be *BufferEvent) expired() bool { 17 | if be.expires != nil { 18 | return be.expires.Before(time.Now()) 19 | } 20 | return true 21 | } 22 | 23 | type RingBuffer struct { 24 | sync.RWMutex 25 | 26 | items map[uint64]*BufferEvent 27 | itemOrder []uint64 28 | size int 29 | position int 30 | ttl time.Duration 31 | } 32 | 33 | func NewRingBuffer(size int, duration time.Duration) *RingBuffer { 34 | logrus.WithFields(logrus.Fields{ 35 | "size": size, 36 | "timeout": duration, 37 | }).Info("Creating retry buffer.") 38 | 39 | r := &RingBuffer{ 40 | items: make(map[uint64]*BufferEvent), 41 | itemOrder: make([]uint64, size), 42 | size: size, 43 | ttl: duration, 44 | } 45 | 46 | if r.enabled() && r.ttl > 0 { 47 | r.startCleanupTimer() 48 | } 49 | 50 | return r 51 | } 52 | 53 | func (r *RingBuffer) enabled() bool { 54 | return r.size > 0 55 | } 56 | 57 | // Add an event to ring buffer 58 | func (r *RingBuffer) Add(key uint64, ev *event.Event) { 59 | // fast return if disabled 60 | if !r.enabled() { 61 | return 62 | } 63 | 64 | expiration := time.Now().Add(r.ttl) 65 | be := &BufferEvent{ 66 | Event: ev, 67 | expires: &expiration, 68 | } 69 | 70 | r.Lock() 71 | // check if we have an existing event at this position 72 | oldKey := r.itemOrder[r.position] 73 | if oldKey != 0 { 74 | // If the key at the current position is associated with an event 75 | // in the buffer, remove it. Otherwise, this delete is a NOOP. 76 | delete(r.items, oldKey) 77 | } 78 | 79 | // add event to map and order array 80 | r.items[key] = be 81 | r.itemOrder[r.position] = key 82 | 83 | // increment position 84 | r.position++ 85 | if r.position >= r.size { 86 | r.position = 0 87 | } 88 | r.Unlock() 89 | } 90 | 91 | // Get an event from the buffer's map 92 | func (r *RingBuffer) Get(key uint64) (*event.Event, bool) { 93 | // fast return if disabled 94 | if !r.enabled() { 95 | return nil, false 96 | } 97 | 98 | r.RLock() 99 | be, ok := r.items[key] 100 | r.RUnlock() 101 | if ok { 102 | return be.Event, true 103 | } else { 104 | return nil, false 105 | } 106 | } 107 | 108 | func (r *RingBuffer) cleanup() { 109 | r.Lock() 110 | beforeSize := len(r.items) 111 | for key, be := range r.items { 112 | if be.expired() { 113 | delete(r.items, key) 114 | } 115 | } 116 | afterSize := len(r.items) 117 | r.Unlock() 118 | 119 | logrus.WithFields(logrus.Fields{ 120 | "beforeSize": beforeSize, 121 | "afterSize": afterSize, 122 | }).Debug("Retry buffer cleanup") 123 | } 124 | 125 | func (r *RingBuffer) startCleanupTimer() { 126 | duration := r.ttl 127 | if duration < time.Second { 128 | duration = time.Second 129 | } 130 | go (func() { 131 | for range time.Tick(duration) { 132 | r.cleanup() 133 | } 134 | })() 135 | } 136 | -------------------------------------------------------------------------------- /transmission/ringbuffer_test.go: -------------------------------------------------------------------------------- 1 | package transmission 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/honeycombio/honeycomb-kubernetes-agent/event" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGet(t *testing.T) { 12 | rb := NewRingBuffer(100, 0) 13 | 14 | assert.True(t, rb.enabled(), "A buffer of size greater than 0 is enabled") 15 | 16 | for i := uint64(0); i < 10; i++ { 17 | ev := &event.Event{ 18 | Data: map[string]interface{}{ 19 | "item": i, 20 | }, 21 | } 22 | rb.Add(i, ev) 23 | } 24 | 25 | ev, ok := rb.Get(5) 26 | assert.True(t, ok, "Expected event wasn't found in the buffer") 27 | assert.Equal(t, uint64(5), ev.Data["item"], "Event found didn't match expected data") 28 | 29 | ev, ok = rb.Get(8) 30 | assert.True(t, ok, "Expected event wasn't found in the buffer") 31 | assert.Equal(t, uint64(8), ev.Data["item"], "Event found didn't match expected data") 32 | } 33 | 34 | func TestGetWhenDisabled(t *testing.T) { 35 | rb := NewRingBuffer(0, 0) 36 | 37 | assert.False(t, rb.enabled(), "A buffer of size 0 is not enabled") 38 | 39 | for i := uint64(0); i < 10; i++ { 40 | ev := &event.Event{ 41 | Data: map[string]interface{}{ 42 | "item": i, 43 | }, 44 | } 45 | rb.Add(i, ev) 46 | } 47 | 48 | ev, ok := rb.Get(5) 49 | assert.False(t, ok, "Retrieving an event from a disabled buffer is not ok") 50 | assert.Nil(t, ev, "A disabled buffer will return nil, not an event") 51 | } 52 | 53 | func TestRingOverflow(t *testing.T) { 54 | rb := NewRingBuffer(100, 0) 55 | 56 | for i := uint64(0); i < 100; i++ { 57 | ev := &event.Event{ 58 | Data: map[string]interface{}{ 59 | "item": i, 60 | }, 61 | } 62 | rb.Add(i, ev) 63 | } 64 | 65 | for i := uint64(100); i < 110; i++ { 66 | ev := &event.Event{ 67 | Data: map[string]interface{}{ 68 | "item": i, 69 | }, 70 | } 71 | rb.Add(i, ev) 72 | } 73 | 74 | _, ok := rb.Get(5) 75 | assert.False(t, ok, "Found an event on the ring that should have been evicted by the size limit") 76 | 77 | ev, ok := rb.Get(105) 78 | assert.True(t, ok, "Expected event wasn't found in the buffer") 79 | assert.Equal(t, uint64(105), ev.Data["item"], "Event found didn't match expected data") 80 | } 81 | 82 | func TestExpire(t *testing.T) { 83 | rb := NewRingBuffer(100, 1*time.Second) 84 | 85 | // this range of events will expire 86 | for i := uint64(0); i < 10; i++ { 87 | ev := &event.Event{ 88 | Data: map[string]interface{}{ 89 | "item": i, 90 | }, 91 | } 92 | rb.Add(i, ev) 93 | } 94 | 95 | // sleep for more than the TTL 96 | time.Sleep(2 * time.Second) 97 | 98 | // this range of events will be alive 99 | for i := uint64(10); i < 20; i++ { 100 | ev := &event.Event{ 101 | Data: map[string]interface{}{ 102 | "item": i, 103 | }, 104 | } 105 | rb.Add(i, ev) 106 | } 107 | 108 | _, ok := rb.Get(5) 109 | assert.False(t, ok, "Found an event on the ring that should have expired") 110 | 111 | ev, ok := rb.Get(15) 112 | assert.True(t, ok, "Expected unexpired event wasn't found") 113 | assert.Equal(t, uint64(15), ev.Data["item"], "Event found didn't match expected data") 114 | } 115 | -------------------------------------------------------------------------------- /metrics/resource.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "time" 5 | 6 | "k8s.io/apimachinery/pkg/types" 7 | stats "k8s.io/kubelet/pkg/apis/stats/v1alpha1" 8 | ) 9 | 10 | type Resource struct { 11 | Type string 12 | Name string 13 | Labels map[string]string 14 | Timestamp time.Time 15 | Status map[string]interface{} 16 | PodMetadata *PodMetadata 17 | } 18 | 19 | func getNodeResource(s stats.NodeStats, metadata *Metadata) *Resource { 20 | labels := map[string]string{ 21 | LabelNodeName: s.NodeName, 22 | } 23 | if metadata.IncludeNodeLabels { 24 | nodeMetadata, err := metadata.GetNodeMetadataByName(s.NodeName) 25 | if err == nil { 26 | for k, v := range nodeMetadata.GetLabels() { 27 | labels[PrefixLabel+k] = v 28 | } 29 | } 30 | } 31 | resource := &Resource{ 32 | Type: "node", 33 | Name: s.NodeName, 34 | Labels: labels, 35 | } 36 | 37 | if s.CPU != nil { 38 | resource.Timestamp = s.CPU.Time.Time 39 | } 40 | 41 | return resource 42 | } 43 | 44 | func getPodResource(node *Resource, s stats.PodStats, metadata *Metadata) *Resource { 45 | labels := map[string]string{ 46 | LabelNamespaceName: s.PodRef.Namespace, 47 | LabelPodUid: s.PodRef.UID, 48 | LabelPodName: s.PodRef.Name, 49 | } 50 | 51 | resource := &Resource{ 52 | Type: "pod", 53 | Name: s.PodRef.Name, 54 | Labels: labels, 55 | } 56 | 57 | if s.CPU != nil { 58 | resource.Timestamp = s.CPU.Time.Time 59 | } 60 | 61 | podMetadata, err := metadata.GetPodMetadataByUid(types.UID(s.PodRef.UID)) 62 | if err != nil { 63 | return resource 64 | } 65 | 66 | status := podMetadata.GetStatus() 67 | addAdditionalLabels(labels, node.Labels, podMetadata) 68 | 69 | resource.Status = status 70 | resource.PodMetadata = podMetadata 71 | 72 | return resource 73 | 74 | } 75 | 76 | func getContainerResource(pod *Resource, s stats.ContainerStats) (*Resource, error) { 77 | labels := map[string]string{ 78 | LabelContainerName: s.Name, 79 | } 80 | 81 | addAdditionalLabels(labels, pod.Labels, pod.PodMetadata) 82 | 83 | status := pod.PodMetadata.GetStatusForContainer(s.Name) 84 | 85 | resource := &Resource{ 86 | Type: "container", 87 | Name: s.Name, 88 | Labels: labels, 89 | Status: status, 90 | PodMetadata: pod.PodMetadata, 91 | } 92 | if s.CPU != nil { 93 | resource.Timestamp = s.CPU.Time.Time 94 | } 95 | return resource, nil 96 | } 97 | 98 | func getVolumeResource(pod *Resource, s stats.VolumeStats) *Resource { 99 | labels := map[string]string{ 100 | LabelVolumeName: s.Name, 101 | } 102 | 103 | addAdditionalLabels(labels, pod.Labels, pod.PodMetadata) 104 | 105 | return &Resource{ 106 | Type: "volume", 107 | Name: s.Name, 108 | Labels: labels, 109 | Timestamp: s.Time.Time, 110 | PodMetadata: pod.PodMetadata, 111 | } 112 | } 113 | 114 | func addAdditionalLabels(labels map[string]string, resLabels map[string]string, podMetadata *PodMetadata) { 115 | // add resource labels 116 | for k, v := range resLabels { 117 | labels[k] = v 118 | } 119 | 120 | podLabels := podMetadata.GetLabels() 121 | for k, v := range podLabels { 122 | labels[PrefixLabel+k] = v 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "time" 7 | 8 | "github.com/honeycombio/honeycomb-kubernetes-agent/metrics" 9 | 10 | yaml "gopkg.in/yaml.v2" 11 | ) 12 | 13 | type Config struct { 14 | APIKey string `yaml:"apiKey"` 15 | APIHost string `yaml:"apiHost"` 16 | // Deprecated: use APIKey instead. 17 | WriteKey string `yaml:"writekey"` 18 | RetryBufferSize int `yaml:"retryBufferSize"` 19 | RetryBufferExpire time.Duration `yaml:"retryBufferExpire"` 20 | Watchers []*WatcherConfig 21 | Verbosity string 22 | LegacyLogPaths bool `yaml:"legacyLogPaths"` 23 | SplitLogging bool `yaml:"splitLogging"` 24 | AdditionalFields map[string]interface{} `yaml:"additionalFields"` 25 | Metrics *MetricsConfig 26 | } 27 | 28 | type WatcherConfig struct { 29 | Parser *ParserConfig 30 | Dataset string 31 | Namespace string 32 | // Distinguish between nil and empty string in the LabelSelector 33 | // nil means watch no pods, empty string means watch all of them 34 | // Maybe we need a better API? But k8s is pretty insistent that empty 35 | // string means "select all pods". 36 | LabelSelector *string `yaml:"labelSelector"` 37 | FilePaths []string `yaml:"paths"` 38 | ExcludePaths []string `yaml:"exclude"` 39 | ContainerName string `yaml:"containerName"` 40 | Processors []map[string]map[string]interface{} 41 | } 42 | 43 | type ParserConfig struct { 44 | Name string 45 | Options map[string]interface{} 46 | } 47 | 48 | type MetricsConfig struct { 49 | Enabled bool 50 | Dataset string 51 | Endpoint string 52 | Interval time.Duration 53 | ClusterName string `yaml:"clusterName"` 54 | // Labels to omit from becoming fields in Honeycomb 55 | // By default `controller-revision-hash` is omitted 56 | OmitLabels []metrics.OmitLabel `yaml:"omitLabels"` 57 | // Include node metadata such as 'node.kubernetes.io/instance-type' or 58 | // 'topology.kubernetes.io/region' 59 | IncludeNodeLabels bool `yaml:"includeNodeLabels"` 60 | // MetricGroupsToCollect provides a list of metrics groups to collect metrics from. 61 | // "container", "pod", "node" and "volume" are the only valid groups. 62 | MetricGroups []metrics.MetricGroup `yaml:"metricGroups"` 63 | AdditionalFields map[string]interface{} `yaml:"additionalFields"` 64 | } 65 | 66 | func (p *ParserConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { 67 | var name string 68 | if err := unmarshal(&name); err == nil { 69 | p.Name = name 70 | return nil 71 | } 72 | aux := &struct { 73 | Name string 74 | Options map[string]interface{} 75 | }{} 76 | err := unmarshal(&aux) 77 | if err == nil { 78 | p.Name = aux.Name 79 | p.Options = aux.Options 80 | return nil 81 | } 82 | return err 83 | } 84 | 85 | func ReadFromFile(filePath string) (*Config, error) { 86 | contents, err := ioutil.ReadFile(filePath) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | config := &Config{} 92 | if err = yaml.Unmarshal(contents, config); err != nil { 93 | return nil, err 94 | } 95 | 96 | for _, watcher := range config.Watchers { 97 | if watcher.FilePaths != nil && watcher.LabelSelector != nil { 98 | return nil, fmt.Errorf("cannot configure both labelSelector and paths") 99 | } 100 | } 101 | 102 | return config, nil 103 | } 104 | -------------------------------------------------------------------------------- /parsers/nginx.go: -------------------------------------------------------------------------------- 1 | package parsers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/honeycombio/gonx" 10 | ) 11 | 12 | // nginx's default log format 13 | const defaultLogFormat = `$remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"` 14 | 15 | // envoy's default log format 16 | // https://envoyproxy.github.io/envoy/configuration/http_conn_man/access_log.html#config-http-con-manager-access-log-default-format 17 | const envoyLogFormat = `[$timestamp] "$request" $status_code $response_flags $bytes_received $bytes_sent $duration $x_envoy_upstream_service_time "$x_forwarded_for" "$user_agent" "$x_request_id" "$authority" "$upstream_host"` 18 | 19 | // nginx ingress default log format 20 | // https://github.com/kubernetes/ingress-nginx/blob/9c6201b79a8b4/internal/ingress/controller/config/config.go#L53 21 | const nginxIngressLogFormat = `$the_real_ip - [$the_real_ip] - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $req_id` 22 | 23 | type NginxParserFactory struct { 24 | parserName string 25 | logFormat string 26 | } 27 | 28 | func (pf *NginxParserFactory) Init(options map[string]interface{}) error { 29 | logFormat := defaultLogFormat 30 | if pf.parserName == "envoy" { 31 | logFormat = envoyLogFormat 32 | } 33 | if pf.parserName == "nginx-ingress" { 34 | logFormat = nginxIngressLogFormat 35 | } 36 | if logFormatOption, ok := options["log_format"]; ok { 37 | typedLogFormatOption, ok := logFormatOption.(string) 38 | if !ok { 39 | return fmt.Errorf("Unexpected type for log_format option (expected string, got %v", reflect.TypeOf(logFormatOption)) 40 | } 41 | 42 | switch typedLogFormatOption { 43 | case "default": 44 | logFormat = defaultLogFormat 45 | case "envoy": 46 | logFormat = envoyLogFormat 47 | case "nginx-ingress": 48 | logFormat = nginxIngressLogFormat 49 | default: 50 | logFormat = typedLogFormatOption 51 | } 52 | } 53 | 54 | pf.logFormat = logFormat 55 | return nil 56 | } 57 | 58 | func (pf *NginxParserFactory) New() Parser { 59 | return &NginxParser{ 60 | gonxParser: gonx.NewParser(pf.logFormat), 61 | } 62 | } 63 | 64 | type NginxParser struct { 65 | gonxParser *gonx.Parser 66 | } 67 | 68 | // This is basically lifted from honeytail 69 | 70 | func (p *NginxParser) Parse(line string) (map[string]interface{}, error) { 71 | gonxEvent, err := p.gonxParser.ParseString(line) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return typeifyParsedLine(gonxEvent.Fields), nil 76 | } 77 | 78 | // typeifyParsedLine attempts to cast numbers in the event to floats or ints 79 | func typeifyParsedLine(pl map[string]string) map[string]interface{} { 80 | // try to convert numbers, if possible 81 | msi := make(map[string]interface{}, len(pl)) 82 | for k, v := range pl { 83 | switch { 84 | case strings.Contains(v, "."): 85 | f, err := strconv.ParseFloat(v, 64) 86 | if err == nil { 87 | msi[k] = f 88 | continue 89 | } 90 | case v == "-": 91 | // no value, don't set a "-" string 92 | continue 93 | default: 94 | i, err := strconv.ParseInt(v, 10, 64) 95 | if err == nil { 96 | msi[k] = i 97 | continue 98 | } 99 | } 100 | msi[k] = v 101 | } 102 | return msi 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cluster-level Kubernetes Logging with Honeycomb 2 | 3 | [![OSS Lifecycle](https://img.shields.io/osslifecycle/honeycombio/honeycomb-kubernetes-agent?color=success)](https://github.com/honeycombio/home/blob/main/honeycomb-oss-lifecycle-and-practices.md) 4 | [![CircleCI](https://circleci.com/gh/honeycombio/honeycomb-kubernetes-agent.svg?style=shield)](https://circleci.com/gh/honeycombio/honeycomb-kubernetes-agent) 5 | 6 | [Honeycomb's](https://honeycomb.io) Kubernetes agent aggregates logs across a Kubernetes cluster. Stop managing log storage in all your clusters and start tracking down real problems. 7 | 8 | To get started with Honeycomb, check out the [Honeycomb general quickstart](https://docs.honeycomb.io/getting-started/quickstart/). 9 | 10 | This README includes some basic information about getting started with the 11 | Kubernetes agent. Please see Honeycomb's 12 | [Kubernetes documentation](https://honeycomb.io/docs/connect/kubernetes/) for 13 | more comprehensive documentation. 14 | 15 | ## How it Works 16 | 17 | `honeycomb-agent` runs as a [DaemonSet](https://kubernetes.io/docs/admin/daemons/) on each node in a cluster. It reads container log files from the node's filesystem, augments them with metadata from the Kubernetes API, and ships them to Honeycomb so that you can see what's going on. 18 | 19 | architecture diagram 20 | 21 | ## Quickstart 22 | 23 | The following steps will deploy the Honeycomb agent to each node in your cluster, and configure it to process logs from all pods. 24 | 25 | 1. Grab your Honeycomb writekey from your [account page](https://ui.honeycomb.io/account), and create a Kubernetes secret from it: 26 | ``` 27 | kubectl create secret generic honeycomb --from-literal=api-key=$WRITEKEY --namespace=honeycomb 28 | ``` 29 | 30 | 2. Run the agent 31 | ``` 32 | kubectl apply -f examples/quickstart.yaml 33 | ``` 34 | This will do three things: 35 | - create a service account for the agent so that it can list pods from the API 36 | - create a minimal `ConfigMap` containing configuration for the agent 37 | - create a DaemonSet from the agent. 38 | 39 | ## Production-Ready Use 40 | 41 | ### Service-specific parsing 42 | 43 | It's best if all of your containers output structured JSON logs. But that's not 44 | always realistic. In particular, you're likely to operate third-party services, 45 | such as proxies or databases, that don't log JSON. 46 | 47 | You may also want to aggregate logs from specific services, rather than from 48 | everything that might be running in a cluster. 49 | 50 | In order to get usefully structured data from services, you can use Kubernetes [label 51 | selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) 52 | to describe how to parse logs for specific services. 53 | 54 | For example, to parse logs from pods with the label `app: nginx` as NGINX logs, 55 | you'd specify the following configuration: 56 | 57 | ``` 58 | watchers: 59 | - labelSelector: "app=nginx" 60 | dataset: kubernetes-nginx 61 | parser: nginx 62 | ``` 63 | ### Post-Processing Events 64 | 65 | You might want to do additional munging of events before sending them to 66 | Honeycomb. For each label selector, you can specify a list of `processors`, 67 | which will be applied in order. For example: 68 | 69 | ``` 70 | watchers: 71 | - labelSelector: "app=nginx" 72 | parser: nginx 73 | dataset: kubernetes-nginx 74 | processors: 75 | - request_shape: # Unpack the field "request": "GET /path HTTP/1.x" 76 | field: request # into its constituent components 77 | 78 | - drop_field: # Remove the "user_email" field from all events 79 | field: user_email 80 | 81 | - sample: # Sample events: only send one in 20 82 | type: static 83 | rate: 20 84 | ``` 85 | 86 | See the [docs](/docs/example-configurations.md) for more examples. 87 | -------------------------------------------------------------------------------- /metrics/accumulator.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/sirupsen/logrus" 7 | 8 | stats "k8s.io/kubelet/pkg/apis/stats/v1alpha1" 9 | ) 10 | 11 | type MetricGroup string 12 | 13 | const ( 14 | ContainerMetricGroup = MetricGroup("container") 15 | PodMetricGroup = MetricGroup("pod") 16 | NodeMetricGroup = MetricGroup("node") 17 | VolumeMetricGroup = MetricGroup("volume") 18 | ) 19 | 20 | var ValidMetricGroups = map[MetricGroup]bool{ 21 | ContainerMetricGroup: true, 22 | PodMetricGroup: true, 23 | NodeMetricGroup: true, 24 | VolumeMetricGroup: true, 25 | } 26 | 27 | type MetricDataAccumulator struct { 28 | Data []*ResourceMetrics 29 | mp *Processor 30 | metadata *Metadata 31 | metricGroupsToCollect map[MetricGroup]bool 32 | time time.Time 33 | } 34 | 35 | func (a *MetricDataAccumulator) nodeStats(nodeResource *Resource, s stats.NodeStats) { 36 | logrus.WithFields(logrus.Fields{ 37 | "name": nodeResource.Name, 38 | }).Trace("nodeStats") 39 | 40 | if !a.metricGroupsToCollect[NodeMetricGroup] { 41 | return 42 | } 43 | 44 | a.accumulate( 45 | nodeResource, 46 | a.mp.UptimeMetrics(s.StartTime.Time), 47 | a.mp.CpuMetrics(s.CPU, 0), 48 | a.mp.FsMetrics(s.Fs), 49 | a.mp.MemMetrics(s.Memory, float64(*s.Memory.AvailableBytes)), 50 | a.mp.NetworkMetrics(s.Network), 51 | ) 52 | } 53 | 54 | func (a *MetricDataAccumulator) podStats(podResource *Resource, s stats.PodStats) { 55 | logrus.WithFields(logrus.Fields{ 56 | "name": podResource.Name, 57 | }).Trace("podStats") 58 | 59 | if !a.metricGroupsToCollect[PodMetricGroup] { 60 | return 61 | } 62 | 63 | // Metatdata can be nil if pod is terminated before metadata is fetched. 64 | // no metrics are needed. 65 | if podResource.PodMetadata == nil { 66 | return 67 | } 68 | 69 | a.accumulate( 70 | podResource, 71 | a.mp.UptimeMetrics(s.StartTime.Time), 72 | a.mp.CpuMetrics(s.CPU, podResource.PodMetadata.GetCpuLimit()), 73 | a.mp.FsMetrics(s.EphemeralStorage), 74 | a.mp.MemMetrics(s.Memory, podResource.PodMetadata.GetMemoryLimit()), 75 | a.mp.NetworkMetrics(s.Network), 76 | ) 77 | } 78 | 79 | func (a *MetricDataAccumulator) containerStats(podResource *Resource, s stats.ContainerStats) { 80 | logrus.WithFields(logrus.Fields{ 81 | "podName": podResource.Name, 82 | "name": s.Name, 83 | }).Trace("containerStats") 84 | 85 | if !a.metricGroupsToCollect[ContainerMetricGroup] { 86 | return 87 | } 88 | 89 | if s.CPU == nil { 90 | return 91 | } 92 | 93 | resource, err := getContainerResource(podResource, s) 94 | 95 | if err != nil { 96 | logrus.WithFields(logrus.Fields{ 97 | "pod": podResource.Labels[LabelPodName], 98 | "container": podResource.Labels[LabelContainerName], 99 | }).Warn("failed to fetch container metrics") 100 | return 101 | } 102 | 103 | a.accumulate( 104 | resource, 105 | a.mp.UptimeMetrics(s.StartTime.Time), 106 | a.mp.CpuMetrics(s.CPU, resource.PodMetadata.GetCpuLimitForContainer(s.Name)), 107 | a.mp.MemMetrics(s.Memory, podResource.PodMetadata.GetMemoryLimitForContainer(s.Name)), 108 | a.mp.FsMetrics(s.Rootfs), 109 | ) 110 | } 111 | 112 | func (a *MetricDataAccumulator) volumeStats(podResource *Resource, s stats.VolumeStats) { 113 | logrus.WithFields(logrus.Fields{ 114 | "podName": podResource.Name, 115 | "name": s.Name, 116 | }).Trace("volumeStats") 117 | 118 | if !a.metricGroupsToCollect[VolumeMetricGroup] { 119 | return 120 | } 121 | 122 | volume := getVolumeResource(podResource, s) 123 | 124 | a.accumulate( 125 | volume, 126 | a.mp.VolumeMetrics(s), 127 | ) 128 | } 129 | 130 | func (a *MetricDataAccumulator) accumulate(r *Resource, m ...Metrics) { 131 | retMetrics := make(Metrics) 132 | 133 | for _, metrics := range m { 134 | for k, v := range metrics { 135 | retMetrics[k] = v 136 | } 137 | } 138 | a.Data = append(a.Data, &ResourceMetrics{ 139 | Resource: r, 140 | Metrics: retMetrics, 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /handlers/handlers.go: -------------------------------------------------------------------------------- 1 | // Package handlers drives the actual processing of log lines. For each set of 2 | // files that you want treated in a specific way, you create a 3 | // `LineHandlerFactory`. `LineHandlerFactory.New()` then creates a new 4 | // `LineHandler` for each specific file. (This mechanism lets parsers be 5 | // stateful, because you end up with one parser instance per file.) 6 | package handlers 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/honeycombio/honeycomb-kubernetes-agent/config" 12 | "github.com/honeycombio/honeycomb-kubernetes-agent/parsers" 13 | "github.com/honeycombio/honeycomb-kubernetes-agent/processors" 14 | "github.com/honeycombio/honeycomb-kubernetes-agent/transmission" 15 | "github.com/honeycombio/honeycomb-kubernetes-agent/unwrappers" 16 | "github.com/sirupsen/logrus" 17 | ) 18 | 19 | type LineHandler interface { 20 | Handle(string) 21 | } 22 | 23 | type LineHandlerFactory interface { 24 | New(path string) LineHandler 25 | } 26 | 27 | type LineHandlerFactoryImpl struct { 28 | config *config.WatcherConfig 29 | unwrapper unwrappers.Unwrapper 30 | parserFactory parsers.ParserFactory 31 | processors []processors.Processor 32 | transmitter transmission.Transmitter 33 | } 34 | 35 | func NewLineHandlerFactoryFromConfig( 36 | config *config.WatcherConfig, 37 | unwrapper unwrappers.Unwrapper, 38 | transmitter transmission.Transmitter, 39 | extraProcessors ...processors.Processor, 40 | ) (*LineHandlerFactoryImpl, error) { 41 | ret := &LineHandlerFactoryImpl{ 42 | config: config, 43 | unwrapper: unwrapper, 44 | transmitter: transmitter, 45 | } 46 | if config.Dataset == "" { 47 | return nil, fmt.Errorf("Missing dataset in configuration") 48 | } 49 | if config.Parser == nil { 50 | return nil, fmt.Errorf("No parser specified") 51 | } 52 | 53 | parserFactory, err := parsers.NewParserFactory(config.Parser) 54 | if err != nil { 55 | return nil, fmt.Errorf("Error setting up parser: %v", err) 56 | } 57 | ret.parserFactory = parserFactory 58 | 59 | for _, processorConfig := range config.Processors { 60 | processor, err := processors.NewProcessorFromConfig(processorConfig) 61 | if err != nil { 62 | return nil, fmt.Errorf("Error setting up processor: %v", err) 63 | } 64 | ret.processors = append(ret.processors, processor) 65 | } 66 | for _, ep := range extraProcessors { 67 | if _, ok := ep.(*processors.KubernetesMetadataProcessor); ok { 68 | // put the K8s metadata processor first in line (see #296) 69 | ret.processors = append([]processors.Processor{ep}, ret.processors...) 70 | } else { 71 | ret.processors = append(ret.processors, ep) 72 | } 73 | } 74 | return ret, nil 75 | } 76 | 77 | func (hf *LineHandlerFactoryImpl) New(path string) LineHandler { 78 | logrus.WithFields(logrus.Fields{ 79 | "path": path, 80 | "parser": hf.config.Parser.Name, 81 | }).Info("Initializing file handler") 82 | handler := &LineHandlerImpl{ 83 | path: path, 84 | config: hf.config, 85 | parser: hf.parserFactory.New(), 86 | processors: hf.processors, 87 | unwrapper: hf.unwrapper, 88 | } 89 | handler.transmitter = hf.transmitter 90 | return handler 91 | } 92 | 93 | type LineHandlerImpl struct { 94 | path string 95 | config *config.WatcherConfig 96 | unwrapper unwrappers.Unwrapper 97 | parser parsers.Parser 98 | processors []processors.Processor 99 | transmitter transmission.Transmitter 100 | } 101 | 102 | func (h *LineHandlerImpl) Handle(rawLine string) { 103 | event, err := h.unwrapper.Unwrap(rawLine, h.parser) 104 | if err != nil { 105 | logrus.WithError(err).Debug("Failed to parse line") 106 | return 107 | } 108 | if event == nil { 109 | // No error, but no event produced (e.g., the line produced 110 | // something the parser thinks is incomplete). 111 | // TODO: is there a better way to handle this? 112 | return 113 | } 114 | event.Dataset = h.config.Dataset 115 | event.Path = h.path 116 | for _, p := range h.processors { 117 | ret := p.Process(event) 118 | if !ret { 119 | logrus.Debug("Dropping line after processing") 120 | return 121 | } 122 | } 123 | logrus.WithField("parsed", event).Trace("Sending line") 124 | h.transmitter.Send(event) 125 | } 126 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/honeycombio/honeycomb-kubernetes-agent 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/bmatcuk/doublestar/v4 v4.8.1 7 | github.com/boltdb/bolt v1.3.1 8 | github.com/hashicorp/golang-lru v1.0.2 9 | github.com/honeycombio/dynsampler-go v0.6.0 10 | github.com/honeycombio/gonx v1.3.1-0.20171118020637-f9b2468e9ef8 11 | github.com/honeycombio/honeytail v1.10.0 12 | github.com/honeycombio/libhoney-go v1.25.0 13 | github.com/honeycombio/urlshaper v0.0.0-20240306233602-40940cefe5f9 14 | github.com/hpcloud/tail v1.0.1-0.20170814160653-37f427138745 15 | github.com/jessevdk/go-flags v1.6.1 16 | github.com/kr/logfmt v0.0.0-20210122060352-19f9bcb100e6 17 | github.com/mitchellh/mapstructure v1.5.0 18 | github.com/pkg/errors v0.9.1 19 | github.com/sirupsen/logrus v1.9.3 20 | github.com/stretchr/testify v1.10.0 21 | gopkg.in/yaml.v2 v2.4.0 22 | k8s.io/api v0.32.3 23 | k8s.io/apimachinery v0.32.3 24 | k8s.io/client-go v0.32.3 25 | k8s.io/kubelet v0.32.3 26 | ) 27 | 28 | require ( 29 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 30 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 31 | github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect 32 | github.com/facebookgo/limitgroup v0.0.0-20150612190941-6abd8d71ec01 // indirect 33 | github.com/facebookgo/muster v0.0.0-20150708232844-fd3d7953fd52 // indirect 34 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 35 | github.com/go-logr/logr v1.4.2 // indirect 36 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 37 | github.com/go-openapi/jsonreference v0.21.0 // indirect 38 | github.com/go-openapi/swag v0.23.0 // indirect 39 | github.com/gogo/protobuf v1.3.2 // indirect 40 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 41 | github.com/golang/protobuf v1.5.4 // indirect 42 | github.com/google/gnostic-models v0.6.8 // indirect 43 | github.com/google/go-cmp v0.6.0 // indirect 44 | github.com/google/go-licenses/v2 v2.0.0-alpha.1 // indirect 45 | github.com/google/gofuzz v1.2.0 // indirect 46 | github.com/google/licenseclassifier/v2 v2.0.0 // indirect 47 | github.com/google/uuid v1.6.0 // indirect 48 | github.com/gopherjs/gopherjs v1.17.2 // indirect 49 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 50 | github.com/josharian/intern v1.0.0 // indirect 51 | github.com/json-iterator/go v1.1.12 // indirect 52 | github.com/jtolds/gls v4.20.0+incompatible // indirect 53 | github.com/klauspost/compress v1.17.11 // indirect 54 | github.com/mailru/easyjson v0.7.7 // indirect 55 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 56 | github.com/modern-go/reflect2 v1.0.2 // indirect 57 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 58 | github.com/otiai10/copy v1.10.0 // indirect 59 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 60 | github.com/sergi/go-diff v1.2.0 // indirect 61 | github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect 62 | github.com/spf13/cobra v1.8.1 // indirect 63 | github.com/spf13/pflag v1.0.5 // indirect 64 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 65 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 66 | github.com/x448/float16 v0.8.4 // indirect 67 | go.opencensus.io v0.24.0 // indirect 68 | golang.org/x/mod v0.21.0 // indirect 69 | golang.org/x/net v0.38.0 // indirect 70 | golang.org/x/oauth2 v0.23.0 // indirect 71 | golang.org/x/sync v0.12.0 // indirect 72 | golang.org/x/sys v0.31.0 // indirect 73 | golang.org/x/term v0.30.0 // indirect 74 | golang.org/x/text v0.23.0 // indirect 75 | golang.org/x/time v0.7.0 // indirect 76 | golang.org/x/tools v0.26.0 // indirect 77 | google.golang.org/protobuf v1.35.1 // indirect 78 | gopkg.in/alexcesaro/statsd.v2 v2.0.0 // indirect 79 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 80 | gopkg.in/fsnotify.v1 v1.4.7 // indirect 81 | gopkg.in/inf.v0 v0.9.1 // indirect 82 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 83 | gopkg.in/yaml.v3 v3.0.1 // indirect 84 | k8s.io/klog/v2 v2.130.1 // indirect 85 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect 86 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 87 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 88 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect 89 | sigs.k8s.io/yaml v1.4.0 // indirect 90 | ) 91 | 92 | tool github.com/google/go-licenses/v2 93 | -------------------------------------------------------------------------------- /kubelet/client.go: -------------------------------------------------------------------------------- 1 | // Originally inspired by OpenTelemetry Collector kubeletstats receiver 2 | // https://github.com/open-telemetry/opentelemetry-collector 3 | 4 | package kubelet 5 | 6 | import ( 7 | "crypto/tls" 8 | "crypto/x509" 9 | "fmt" 10 | "github.com/sirupsen/logrus" 11 | "io/ioutil" 12 | "net/http" 13 | "os" 14 | 15 | "github.com/pkg/errors" 16 | ) 17 | 18 | const svcAcctCACertPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" 19 | const svcAcctTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" 20 | 21 | type Client interface { 22 | Get(path string) ([]byte, error) 23 | } 24 | 25 | func NewClientProvider(endpoint string) (ClientProvider, error) { 26 | return &saClientProvider{ 27 | endpoint: endpoint, 28 | caCertPath: svcAcctCACertPath, 29 | tokenPath: svcAcctTokenPath, 30 | }, nil 31 | } 32 | 33 | type ClientProvider interface { 34 | BuildClient() (Client, error) 35 | } 36 | 37 | type saClientProvider struct { 38 | endpoint string 39 | caCertPath string 40 | tokenPath string 41 | } 42 | 43 | func (p *saClientProvider) BuildClient() (Client, error) { 44 | rootCAs, err := systemCertPoolPlusPath(p.caCertPath) 45 | if err != nil { 46 | return nil, err 47 | } 48 | tok, err := ioutil.ReadFile(p.tokenPath) 49 | if err != nil { 50 | return nil, errors.WithMessagef(err, "Unable to read token file %s", p.tokenPath) 51 | } 52 | tr := defaultTransport() 53 | tr.TLSClientConfig = &tls.Config{ 54 | RootCAs: rootCAs, 55 | } 56 | return defaultTLSClient(p.endpoint, true, rootCAs, nil, tok) 57 | } 58 | 59 | func defaultTLSClient( 60 | endpoint string, 61 | insecureSkipVerify bool, 62 | rootCAs *x509.CertPool, 63 | certificates []tls.Certificate, 64 | tok []byte, 65 | ) (*clientImpl, error) { 66 | tr := defaultTransport() 67 | tr.TLSClientConfig = &tls.Config{ 68 | RootCAs: rootCAs, 69 | Certificates: certificates, 70 | InsecureSkipVerify: insecureSkipVerify, 71 | } 72 | if endpoint == "" { 73 | var err error 74 | endpoint, err = defaultEndpoint() 75 | if err != nil { 76 | return nil, err 77 | } 78 | } 79 | return &clientImpl{ 80 | baseURL: "https://" + endpoint, 81 | httpClient: http.Client{Transport: tr}, 82 | tok: tok, 83 | logger: logrus.WithFields(logrus.Fields{}), 84 | }, nil 85 | } 86 | 87 | // This will work if hostNetwork is turned on, in which case the pod has access 88 | // to the node's loopback device. 89 | // https://kubernetes.io/docs/concepts/policy/pod-security-policy/#host-namespaces 90 | func defaultEndpoint() (string, error) { 91 | hostname, err := os.Hostname() 92 | if err != nil { 93 | return "", errors.WithMessage(err, "Unable to get hostname for default endpoint") 94 | } 95 | const kubeletPort = "10250" 96 | return hostname + ":" + kubeletPort, nil 97 | } 98 | 99 | func defaultTransport() *http.Transport { 100 | return http.DefaultTransport.(*http.Transport).Clone() 101 | } 102 | 103 | // clientImpl 104 | 105 | var _ Client = (*clientImpl)(nil) 106 | 107 | type clientImpl struct { 108 | baseURL string 109 | httpClient http.Client 110 | logger *logrus.Entry 111 | tok []byte 112 | } 113 | 114 | func (c *clientImpl) Get(path string) ([]byte, error) { 115 | req, err := c.buildReq(path) 116 | if err != nil { 117 | return nil, err 118 | } 119 | resp, err := c.httpClient.Do(req) 120 | if err != nil { 121 | return nil, err 122 | } 123 | defer func() { 124 | closeErr := resp.Body.Close() 125 | if closeErr != nil { 126 | c.logger.Warn("failed to close response body: ", closeErr) 127 | } 128 | }() 129 | 130 | body, err := ioutil.ReadAll(resp.Body) 131 | if err != nil { 132 | return nil, errors.WithMessage(err, "failed to read Kubelet response body") 133 | } 134 | 135 | if resp.StatusCode != http.StatusOK { 136 | return nil, fmt.Errorf("kubelet request GET %s failed - %q, response: %q", 137 | req.URL.String(), resp.Status, string(body)) 138 | } 139 | 140 | return body, nil 141 | } 142 | 143 | func (c *clientImpl) buildReq(path string) (*http.Request, error) { 144 | url := c.baseURL + path 145 | req, err := http.NewRequest("GET", url, nil) 146 | if err != nil { 147 | return nil, err 148 | } 149 | req.Header.Set("Content-Type", "application/json") 150 | if c.tok != nil { 151 | req.Header.Set("Authorization", fmt.Sprintf("bearer %s", c.tok)) 152 | } 153 | return req, nil 154 | } 155 | -------------------------------------------------------------------------------- /examples/quickstart.yaml: -------------------------------------------------------------------------------- 1 | # Service account for the agent 2 | --- 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: honeycomb-serviceaccount 7 | namespace: honeycomb 8 | --- 9 | apiVersion: rbac.authorization.k8s.io/v1 10 | kind: ClusterRoleBinding 11 | metadata: 12 | name: honeycomb-serviceaccount 13 | roleRef: 14 | apiGroup: rbac.authorization.k8s.io 15 | kind: ClusterRole 16 | name: honeycomb-serviceaccount 17 | subjects: 18 | - kind: ServiceAccount 19 | name: honeycomb-serviceaccount 20 | namespace: honeycomb 21 | --- 22 | apiVersion: rbac.authorization.k8s.io/v1 23 | kind: ClusterRole 24 | metadata: 25 | name: honeycomb-serviceaccount 26 | namespace: honeycomb 27 | rules: 28 | - apiGroups: 29 | - "" 30 | resources: 31 | - pods 32 | - nodes/stats 33 | - nodes/proxy 34 | verbs: 35 | - list 36 | - watch 37 | - get 38 | 39 | # ConfigMap specifying which logs the agent should watch 40 | --- 41 | apiVersion: v1 42 | kind: ConfigMap 43 | metadata: 44 | name: honeycomb-agent-config 45 | namespace: honeycomb 46 | data: 47 | config.yaml: | 48 | apiHost: https://api.honeycomb.io/ 49 | watchers: 50 | - dataset: kubernetes-logs 51 | labelSelector: "component=kube-apiserver,tier=control-plane" 52 | namespace: kube-system 53 | parser: glog 54 | - dataset: kubernetes-logs 55 | labelSelector: "component=kube-scheduler,tier=control-plane" 56 | namespace: kube-system 57 | parser: glog 58 | - dataset: kubernetes-logs 59 | labelSelector: "component=kube-controller-manager,tier=control-plane" 60 | namespace: kube-system 61 | parser: glog 62 | - dataset: kubernetes-logs 63 | labelSelector: "k8s-app=kube-proxy" 64 | namespace: kube-system 65 | parser: glog 66 | - dataset: kubernetes-logs 67 | labelSelector: "k8s-app=kube-dns" 68 | namespace: kube-system 69 | parser: glog 70 | verbosity: info 71 | splitLogging: false 72 | 73 | metrics: 74 | clusterName: k8s-cluster 75 | dataset: kubernetes-metrics 76 | enabled: true 77 | metricGroups: 78 | - node 79 | - pod 80 | 81 | 82 | # Daemonset 83 | --- 84 | apiVersion: apps/v1 85 | kind: DaemonSet 86 | metadata: 87 | labels: 88 | app: honeycomb-agent 89 | name: honeycomb-agent 90 | namespace: honeycomb 91 | spec: 92 | selector: 93 | matchLabels: 94 | app: honeycomb-agent 95 | updateStrategy: 96 | type: RollingUpdate 97 | rollingUpdate: 98 | maxUnavailable: 1 99 | template: 100 | metadata: 101 | labels: 102 | app: honeycomb-agent 103 | spec: 104 | tolerations: 105 | - operator: Exists 106 | effect: NoSchedule 107 | containers: 108 | - env: 109 | - name: HONEYCOMB_APIKEY 110 | valueFrom: 111 | secretKeyRef: 112 | key: api-key 113 | name: honeycomb 114 | - name: NODE_NAME 115 | valueFrom: 116 | fieldRef: 117 | fieldPath: spec.nodeName 118 | - name: NODE_IP 119 | valueFrom: 120 | fieldRef: 121 | fieldPath: status.hostIP 122 | image: honeycombio/honeycomb-kubernetes-agent:2.7.3 123 | imagePullPolicy: IfNotPresent 124 | name: honeycomb-agent 125 | resources: 126 | limits: 127 | memory: 200Mi 128 | requests: 129 | cpu: 100m 130 | memory: 200Mi 131 | volumeMounts: 132 | - mountPath: "/etc/honeycomb" 133 | name: config 134 | readOnly: false 135 | - mountPath: "/var/log" 136 | name: varlog 137 | readOnly: false 138 | - mountPath: "/var/lib/docker/containers" 139 | name: varlibdockercontainers 140 | readOnly: true 141 | serviceAccountName: honeycomb-serviceaccount 142 | terminationGracePeriodSeconds: 30 143 | volumes: 144 | - configMap: 145 | items: 146 | - key: config.yaml 147 | path: config.yaml 148 | name: honeycomb-agent-config 149 | name: config 150 | - hostPath: 151 | path: "/var/log" 152 | name: varlog 153 | - hostPath: 154 | path: "/var/lib/docker/containers" 155 | name: varlibdockercontainers 156 | --------------------------------------------------------------------------------