[
  {
    "path": ".dockerignore",
    "content": "\n# Created by https://www.gitignore.io/api/go\n\n### Go ###\n# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n*.test\n*.prof\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# external packages folder\nvendor/\n\nbin/\ndist/\n\n.git\n.gitignore\nREADME.md\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\non:\n  push:\n    tags:\n      - \"v*.*.*\"\n\njobs:\n  goreleaser:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: 1.20\n      - name: Run GoReleaser\n        uses: goreleaser/goreleaser-action@v6\n        with:\n          version: latest\n          args: release --rm-dist\n        env:\n          # To upload Homebrew recipe to dtan4/homebrew-tools, we need a personal token\n          # instead of Action's temporary token\n          GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Test\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          cache: true\n          go-version-file: 'go.mod'\n\n      - name: Run tests\n        run: make ci-test\n\n      - name: Send test coverage to Codecov\n        uses: codecov/codecov-action@v5\n\n      - name: Run Trivy vulnerability scanner in repo mode\n        uses: aquasecurity/trivy-action@v0.36.0\n        with:\n          scan-type: \"fs\"\n          ignore-unfixed: true\n          vuln-type: \"os,library\"\n          severity: \"CRITICAL,HIGH\"\n          exit-code: \"1\"\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# Created by https://www.gitignore.io/api/go\n\n### Go ###\n# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n*.test\n*.prof\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# external packages folder\nvendor/\n\n/bin/\n/dist/\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "# This is an example goreleaser.yaml file with some sane defaults.\n# Make sure to check the documentation at http://goreleaser.com\nbefore:\n  hooks:\n    - make clean\n    - go mod tidy\nbuilds:\n- ldflags:\n  - \"-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}\"\n  env:\n  - CGO_ENABLED=0\n  goos:\n  - darwin\n  - linux\n  - windows\n  goarch:\n  - 386\n  - amd64\n  - arm\n  - arm64\narchives:\n- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'\n  replacements:\n    darwin: Darwin\n    linux: Linux\n    windows: Windows\n    386: i386\n    amd64: x86_64\n  format_overrides:\n  - goos: windows\n    format: zip\nrelease:\n  prerelease: auto\nbrews:\n- tap:\n    owner: dtan4\n    name: homebrew-tools\n  folder: Formula\n  homepage: https://github.com/dtan4/k8stail\n  description: \"`tail -f` experience for Kubernetes Pods\"\n  skip_upload: auto # skip if the version is rc (e.g. v1.0.0-rc1)\n  test: |\n    system \"#{bin}/k8stail\", \"-v\"\nchecksum:\n  name_template: 'checksums.txt'\nsnapshot:\n  name_template: \"{{ .Tag }}-next\"\nchangelog:\n  sort: asc\n  filters:\n    exclude:\n    - '^docs:'\n    - '^test:'\n    - Merge pull request\n    - Merge branch\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# [v0.7.0](https://github.com/dtan4/k8stail/releases/tag/v0.7.0) (2022-03-05)\n\n- Go 1.17\n- Update dependencies\n\n# [v0.6.0](https://github.com/dtan4/k8stail/releases/tag/v0.6.0) (2018-07-29)\n\n## Features\n\n- Enable external auth provider [#40](https://github.com/dtan4/k8stail/pull/40)\n\n## Others\n\n- Use Go 1.10.3 on Travis CI [#42](https://github.com/dtan4/k8stail/pull/42)\n- Upgrade to client-go 8.0.0 [#39](https://github.com/dtan4/k8stail/pull/39)\n\n# [v0.5.2.rc1](https://github.com/dtan4/k8stail/releases/tag/v0.5.2.rc1) (2017-04-27)\n\n## Features\n\n- Add debug flag to enable pprof [#31](https://github.com/dtan4/k8stail/pull/31)\n\n## Fixed\n\n- Close goroutine immediately if there is no valid object [#30](https://github.com/dtan4/k8stail/pull/30)\n\n# [v0.5.1](https://github.com/dtan4/k8stail/releases/tag/v0.5.1) (2017-04-27)\n\n## Fixed\n\n- Print selected context correctly [#28](https://github.com/dtan4/k8stail/pull/28)\n\n# [v0.5.0](https://github.com/dtan4/k8stail/releases/tag/v0.5.0) (2017-04-25)\n\n## Features\n\n- Watch Kubernetes events to detect Pod lifecycle correctly [#26](https://github.com/dtan4/k8stail/pull/26)\n- Add more short flags [#25](https://github.com/dtan4/k8stail/pull/25) (thanks @atombender)\n\n# [v0.4.0](https://github.com/dtan4/k8stail/releases/tag/v0.4.0) (2017-04-11)\n\n## Features\n\n- Use default namespace set in kubecfg [#21](https://github.com/dtan4/k8stail/pull/21)\n- Add `--no-halt` flag [#18](https://github.com/dtan4/k8stail/pull/18)\n\n## Fixed\n\n- Detect container recreation [#20](https://github.com/dtan4/k8stail/pull/20)\n\n# [v0.3.0](https://github.com/dtan4/k8stail/releases/tag/v0.3.0) (2016-12-12)\n\n## Backward incompatible changes\n\n- Deprecate `-flag` style flag, use `--flag` [#11](https://github.com/dtan4/k8stail/pull/11)\n\n## Features\n\n- Support context switch by `--context` flag [#13](https://github.com/dtan4/k8stail/pull/13) (Thanks @apstndb)\n\n# [v0.2.1](https://github.com/dtan4/k8stail/releases/tag/v0.2.1) (2016-11-16)\n\nRebuilt binaries to be statically-linked.\n\n# [v0.2.0](https://github.com/dtan4/k8stail/releases/tag/v0.2.0) (2016-11-16)\n\n## Features\n\n- Stream logs of all containers in pod [#5](https://github.com/dtan4/k8stail/pull/5)\n- Get kubeconfig path from KUBECONFIG [#4](https://github.com/dtan4/k8stail/pull/4)\n\n# [v0.1.0](https://github.com/dtan4/k8stail/releases/tag/v0.1.0) (2016-11-15)\n\nInitial release.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:1.26 AS builder\n\nWORKDIR /go/src/github.com/dtan4/k8stail\n\nCOPY go.mod go.sum ./\nRUN go mod download\n\nCOPY . .\nRUN CGO_ENABLED=0 go build -o /k8stail\n\nFROM gcr.io/distroless/static:nonroot\n\nCOPY --from=builder /k8stail /k8stail\n\nENTRYPOINT [\"/k8stail\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Daisuke Fujita\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "NAME    := k8stail\nVERSION := $(shell git tag | sort -V -r | head -n1)-next\nCOMMIT  := $(shell git rev-parse HEAD)\nDATE    := $(shell date \"+%Y-%m-%dT%H:%M:%S%z\")\n\nSRCS    := $(shell find . -name '*.go' -type f)\nLDFLAGS := -ldflags=\"-s -w -X \\\"main.version=$(VERSION)\\\" -X \\\"main.commit=$(COMMIT)\\\" -X \\\"main.date=$(DATE)\\\"\"\n\n.DEFAULT_GOAL := bin/$(NAME)\n\nexport GO111MODULE=on\n\nbin/$(NAME): $(SRCS)\n\tgo build $(LDFLAGS) -o bin/$(NAME)\n\n.PHONY: ci-test\nci-test:\n\tgo test -coverpkg=./... -coverprofile=coverage.txt -v ./...\n\n.PHONY: clean\nclean:\n\trm -rf bin/*\n\n.PHONY: install\ninstall:\n\tgo install $(LDFLAGS)\n\n.PHONY: test\ntest:\n\tgo test -cover -v\n"
  },
  {
    "path": "README.md",
    "content": "# k8stail\n\n[![GitHub Actions](https://github.com/dtan4/k8stail/workflows/Test/badge.svg)](https://github.com/dtan4/k8stail/actions?query=workflow%3ATest+branch%3Amaster)\n[![codecov](https://codecov.io/gh/dtan4/k8stail/branch/master/graph/badge.svg)](https://codecov.io/gh/dtan4/k8stail)\n[![GitHub release](https://img.shields.io/github/release/dtan4/k8stail.svg)](https://github.com/dtan4/k8stail/releases)\n\n`tail -f` experience for Kubernetes Pods\n\nAs you know, `kubectl logs` can stream only ONE pod at the same time. `k8stail` enables you to watch __log streams of ALL pods__ in the specified namespace or labels in real time, like `tail -f`.\n\n![example](_images/example.png)\n\n## Table of Contents\n\n* [Requirements](#requirements)\n* [Installation](#installation)\n  + [Using Homebrew (OS X only)](#using-homebrew-os-x-only)\n  + [Precompiled binary](#precompiled-binary)\n  + [From source](#from-source)\n  + [Run in a Docker container](#run-in-a-docker-container)\n* [Usage](#usage)\n  + [kubeconfig file](#kubeconfig-file)\n  + [Options](#options)\n* [Development](#development)\n* [Author](#author)\n* [License](#license)\n\n## Requirements\n\nKubernetes 1.3 or above\n\n## Installation\n\n### Using Homebrew (OS X only)\n\nFormula is available at [dtan4/homebrew-dtan4](https://github.com/dtan4/homebrew-dtan4).\n\n```bash\n$ brew tap dtan4/dtan4\n$ brew install k8stail\n```\n\n### Precompiled binary\n\nPrecompiled binaries for Windows, OS X, Linux are available at [Releases](https://github.com/dtan4/k8stail/releases).\n\n### From source\n\n```bash\n$ go get -d github.com/dtan4/k8stail\n$ cd $GOPATH/src/github.com/dtan4/k8stail\n$ make deps\n$ make install\n```\n\n### Run in a Docker container\n\nDocker image is no longer provided officially.\nIf you'd like to run k8sec in Docker image, see [`Dockerfile`](Dockerfile) and build image by yourself.\n\n```bash\ndocker build -t k8stail .\n```\n\n## Usage\n\nLogs of all pods, all containers in pod in the specified namespace are streaming. When new pod is added, logs of the pod also appears.\nTo stop streaming and exit, press `Ctrl-C`.\n\n```bash\n$ k8stail --namespace awesome-app\nNamespace: awesome-app\nLabels:\n----------\nPod awesome-app-web-4212725599-67vd4 has detected\nPod awesome-app-web-4212725599-6pduy has detected\nPod awesome-app-web-4212725599-lbuny has detected\nPod awesome-app-web-4212725599-mh3g1 has detected\nPod awesome-app-web-4212725599-pvjsm has detected\n[awesome-app-web-4212725599-mh3g1][web]  | creating base compositions...\n[awesome-app-web-4212725599-zei9h][web]  |    (47.1ms)  CREATE TABLE \"schema_migrations\" (\"version\" character varying NOT NULL)\n[awesome-app-web-4212725599-zei9h][web]  |    (45.1ms)  CREATE UNIQUE INDEX  \"unique_schema_migrations\" ON \"schema_migrations\"  (\"version\")\n[awesome-app-web-4212725599-zei9h][web]  |   ActiveRecord::SchemaMigration Load (1.8ms)  SELECT \"schema_migrations\".* FROM \"schema_migrations\"\n[awesome-app-web-4212725599-zei9h][web]  | Migrating to CreatePosts (20160218082522)\n```\n\nWith `--timestamps` option, log timestamp is printed together.\n\n\n```bash\n$ k8stail --namespace awesome-app --timestamps\nNamespace: awesome-app\nLabels:\n----------\nPod awesome-app-web-4212725599-67vd4 has detected\nPod awesome-app-web-4212725599-6pduy has detected\nPod awesome-app-web-4212725599-lbuny has detected\nPod awesome-app-web-4212725599-mh3g1 has detected\nPod awesome-app-web-4212725599-pvjsm has detected\n[awesome-app-web-4212725599-mh3g1][web] 2016-11-15T10:57:22.178667425Z  | creating base compositions...\n[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.309011520Z  |    (47.1ms)  CREATE TABLE \"schema_migrations\" (\"version\" character varying NOT NULL)\n[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.309053601Z  |    (45.1ms)  CREATE UNIQUE INDEX  \"unique_schema_migrations\" ON \"schema_migrations\"  (\"version\")\n[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.463700110Z  |   ActiveRecord::SchemaMigration Load (1.8ms)  SELECT \"schema_migrations\".* FROM \"schema_migrations\"\n[awesome-app-web-4212725599-zei9h][web] 2016-11-15T10:57:22.463743373Z  | Migrating to CreatePosts (20160218082522)\n```\n\nWith `--labels` option, you can filter pods to watch.\n\n```bash\n$ k8stail --namespace awesome-app --labels name=awesome-app-web\nNamespace: awesome-app\nLabels:    name=awesome-app-web\n----------\nPod awesome-app-web-4212725599-67vd4 has detected\nPod awesome-app-web-4212725599-6pduy has detected\nPod awesome-app-web-4212725599-lbuny has detected\nPod awesome-app-web-4212725599-mh3g1 has detected\nPod awesome-app-web-4212725599-pvjsm has detected\n[awesome-app-web-4212725599-mh3g1][web]  | creating base compositions...\n[awesome-app-web-4212725599-zei9h][web]  |    (47.1ms)  CREATE TABLE \"schema_migrations\" (\"version\" character varying NOT NULL)\n[awesome-app-web-4212725599-zei9h][web]  |    (45.1ms)  CREATE UNIQUE INDEX  \"unique_schema_migrations\" ON \"schema_migrations\"  (\"version\")\n[awesome-app-web-4212725599-zei9h][web]  |   ActiveRecord::SchemaMigration Load (1.8ms)  SELECT \"schema_migrations\".* FROM \"schema_migrations\"\n[awesome-app-web-4212725599-zei9h][web]  | Migrating to CreatePosts (20160218082522)\n```\n\n### kubeconfig file\n\n`k8stail` uses `~/.kube/config` as default. You can specify another path by `KUBECONFIG` environment variable or `--kubeconfig` option. `--kubeconfig` option always overrides `KUBECONFIG` environment variable.\n\n```bash\n$ KUBECONFIG=/path/to/kubeconfig k8stail\n# or\n$ k8stail --kubeconfig=/path/to/kubeconfig\n```\n\n### Options\n\n|Option|Description|Required|Default|\n|---------|-----------|-------|-------|\n|`--context=CONTEXT`|Kubernetes context|||\n|`--debug`|Debug mode using pprof (http://localhost:6060)||`false`|\n|`--kubeconfig=KUBECONFIG`|Path of kubeconfig||`~/.kube/config`|\n|`--labels=LABELS`|Label filter query (e.g. `app=APP,role=ROLE`)|||\n|`--namespace=NAMESPACE`|Kubernetes namespace||`default`|\n|`--timestamps`|Include timestamps on each line||`false`|\n|`-h`, `-help`|Print command line usage|||\n|`-v`, `-version`|Print version|||\n\n## Development\n\nGo 1.7 or above is required.\n\nClone this repository and build using `make`.\n\n```bash\n$ go get -d github.com/dtan4/k8stail\n$ cd $GOPATH/src/github.com/dtan4/k8stail\n$ make\n```\n\n## Author\n\nDaisuke Fujita ([@dtan4](https://github.com/dtan4))\n\n## License\n\n[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)\n"
  },
  {
    "path": "counter.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: counter\n  labels:\n    app: counter\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: counter\n  template:\n    metadata:\n      labels:\n        app: counter\n    spec:\n      containers:\n      - name: counter-a\n        image: busybox\n        args: [\"/bin/sh\", \"-c\", 'i=0; while true; do echo \"$i: $(date)\"; i=$((i+1)); sleep 1; done']\n      - name: counter-b\n        image: busybox\n        args: [\"/bin/sh\", \"-c\", 'i=0; while true; do echo \"$i: $(date)\"; i=$((i+1)); sleep 1; done']\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/dtan4/k8stail\n\ngo 1.26.3\n\nrequire (\n\tgithub.com/fatih/color v1.19.0\n\tgithub.com/spf13/pflag v1.0.10\n\tk8s.io/api v0.36.1\n\tk8s.io/apimachinery v0.36.1\n\tk8s.io/client-go v0.36.1\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.13.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/google/gnostic-models v0.7.0 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/net v0.49.0 // indirect\n\tgolang.org/x/oauth2 v0.34.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/term v0.39.0 // indirect\n\tgolang.org/x/text v0.33.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/klog/v2 v2.140.0 // indirect\n\tk8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect\n\tk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect\n\tsigs.k8s.io/yaml v1.6.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=\ngithub.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=\ngithub.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=\ngithub.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=\ngolang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=\ngolang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=\ngolang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=\ngolang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngoogle.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=\ngoogle.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nk8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=\nk8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=\nk8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=\nk8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=\nk8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=\nk8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=\nk8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=\nk8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=\nk8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=\nk8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=\nk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=\nk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "logger.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/fatih/color\"\n)\n\nvar (\n\tgreenBold  = color.New(color.FgGreen, color.Bold)\n\tyellowBold = color.New(color.FgYellow, color.Bold)\n\tredBold    = color.New(color.FgRed, color.Bold)\n\tboldFunc   = color.New(color.Bold).SprintFunc()\n\tyellowFunc = color.New(color.FgYellow).SprintFunc()\n)\n\n// Logger represents logger\ntype Logger struct {\n\tm sync.Mutex\n}\n\n// NewLogger returns new Logger object\nfunc NewLogger() *Logger {\n\treturn &Logger{\n\t\tm: sync.Mutex{},\n\t}\n}\n\n// PrintColorizedLog prints log with the given color\nfunc (l *Logger) PrintColorizedLog(c *color.Color, line string) {\n\tl.m.Lock()\n\tdefer l.m.Unlock()\n\n\tc.Println(line)\n}\n\n// PrintHeader prints header\nfunc (l *Logger) PrintHeader(context, namespace, labels string) {\n\tfmt.Printf(\"%s %s\\n\", boldFunc(\"Context:  \"), context)\n\tfmt.Printf(\"%s %s\\n\", boldFunc(\"Namespace:\"), namespace)\n\tfmt.Printf(\"%s %s\\n\", boldFunc(\"Labels:   \"), labels)\n\tcolor.New(color.FgYellow).Println(\"Press Ctrl-C to exit.\")\n\tcolor.New(color.Bold).Println(\"----------\")\n}\n\n// PrintPlainLog prints log with no cosmetics\nfunc (l *Logger) PrintPlainLog(line string) {\n\tl.m.Lock()\n\tdefer l.m.Unlock()\n\n\tfmt.Println(line)\n}\n\n// PrintPodDetected prints that Pod was detected\nfunc (l *Logger) PrintPodDetected(pod, container string) {\n\tl.PrintColorizedLog(greenBold, fmt.Sprintf(\"Pod:%s Container:%s has been detected\", pod, container))\n}\n\n// PrintPodDeleted prints that Pod was finished\nfunc (l *Logger) PrintPodFinished(pod, container string) {\n\tl.PrintColorizedLog(yellowBold, fmt.Sprintf(\"Pod:%s Container:%s has been finished\", pod, container))\n}\n\n// PrintPodDeleted prints that Pod was deleted\nfunc (l *Logger) PrintPodDeleted(pod, container string) {\n\tl.PrintColorizedLog(redBold, fmt.Sprintf(\"Pod:%s Container:%s has been deleted\", pod, container))\n}\n\n// PrintPodLog prints Pod log\nfunc (l *Logger) PrintPodLog(pod, container, line string, timestamps bool) {\n\tl.m.Lock()\n\tdefer l.m.Unlock()\n\n\tif timestamps {\n\t\tss := strings.SplitN(line, \" \", 2)\n\t\tfmt.Printf(\"[%s][%s] %s  %s %s \\n\", boldFunc(pod), boldFunc(container), yellowFunc(ss[0]), boldFunc(\"|\"), ss[1])\n\t} else {\n\t\tfmt.Printf(\"[%s][%s]  %s %s\\n\", boldFunc(pod), boldFunc(container), boldFunc(\"|\"), line)\n\t}\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\tflag \"github.com/spf13/pflag\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\nconst (\n\tdebugAddress     = \":6060\"\n\tlogSecondsOffset = 10\n)\n\nvar (\n\tsinceSeconds = int64(math.Ceil(float64(logSecondsOffset) / float64(time.Second)))\n)\n\nfunc main() {\n\tvar (\n\t\tdebug       bool\n\t\tkubeContext string\n\t\tkubeconfig  string\n\t\tlabels      string\n\t\tnamespace   string\n\t\ttimestamps  bool\n\t\tversion     bool\n\t)\n\n\tflags := flag.NewFlagSet(\"k8stail\", flag.ExitOnError)\n\tflags.Usage = func() {\n\t\tflags.PrintDefaults()\n\t}\n\n\tflags.StringVar(&kubeContext, \"context\", \"\", \"Kubernetes context\")\n\tflags.BoolVar(&debug, \"debug\", false, \"Debug mode using pprof (http://localhost:6060)\")\n\tflags.StringVar(&kubeconfig, \"kubeconfig\", \"\", \"Path of kubeconfig\")\n\tflags.StringVarP(&labels, \"labels\", \"l\", \"\", \"Label filter query\")\n\tflags.StringVarP(&namespace, \"namespace\", \"n\", \"\", \"Kubernetes namespace\")\n\tflags.BoolVarP(&timestamps, \"timestamps\", \"t\", false, \"Include timestamps on each line\")\n\tflags.BoolVarP(&version, \"version\", \"v\", false, \"Print version\")\n\n\tif err := flags.Parse(os.Args[1:]); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\tif kubeconfig == \"\" {\n\t\tif os.Getenv(\"KUBECONFIG\") != \"\" {\n\t\t\tkubeconfig = os.Getenv(\"KUBECONFIG\")\n\t\t} else {\n\t\t\tkubeconfig = clientcmd.RecommendedHomeFile\n\t\t}\n\t}\n\n\tif version {\n\t\tprintVersion()\n\t\tos.Exit(0)\n\t}\n\n\tif debug {\n\t\tgo func() {\n\t\t\tlog.Println(http.ListenAndServe(debugAddress, nil))\n\t\t}()\n\t}\n\n\tclientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(\n\t\t&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig},\n\t\t&clientcmd.ConfigOverrides{CurrentContext: kubeContext})\n\n\tconfig, err := clientConfig.ClientConfig()\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\trawConfig, err := clientConfig.RawConfig()\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\tvar currentContext string\n\n\tif kubeContext == \"\" {\n\t\tcurrentContext = rawConfig.CurrentContext\n\t} else {\n\t\tcurrentContext = kubeContext\n\t}\n\n\tif namespace == \"\" {\n\t\tif rawConfig.Contexts[currentContext].Namespace == \"\" {\n\t\t\tnamespace = metav1.NamespaceDefault\n\t\t} else {\n\t\t\tnamespace = rawConfig.Contexts[currentContext].Namespace\n\t\t}\n\t}\n\n\tsigCh := make(chan os.Signal, 1)\n\tsignal.Notify(sigCh, os.Interrupt)\n\n\tlogger := NewLogger()\n\tlogger.PrintHeader(currentContext, namespace, labels)\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\twatcher, err := clientset.CoreV1().Pods(namespace).Watch(ctx, metav1.ListOptions{\n\t\tLabelSelector: labels,\n\t})\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n\tadded, finished, deleted := Watch(ctx, watcher)\n\n\ttails := NewTailMap()\n\n\tgo func() {\n\t\tfor target := range added {\n\t\t\tid := target.GetID()\n\n\t\t\tif _, ok := tails.Get(id); ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttail := NewTail(target.Namespace, target.Pod, target.Container, logger, sinceSeconds, timestamps)\n\t\t\ttails.Set(id, tail)\n\t\t\ttail.Start(ctx, clientset)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tfor target := range finished {\n\t\t\tid := target.GetID()\n\n\t\t\tt, ok := tails.Get(id)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif t.Finished {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tt.Finish()\n\n\t\t\ttails.Delete(id)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tfor target := range deleted {\n\t\t\tid := target.GetID()\n\n\t\t\tt, ok := tails.Get(id)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tt.Delete()\n\n\t\t\ttails.Delete(id)\n\t\t}\n\t}()\n\n\t<-sigCh\n\tcancel()\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:recommended\",\n    \"schedule:weekly\"\n  ],\n  \"postUpdateOptions\": [\n    \"gomodTidy\"\n  ],\n  \"packageRules\": [\n    {\n      \"matchDatasources\": [\"golang-version\"],\n      \"rangeStrategy\": \"bump\"\n    },\n    {\n      \"groupName\": \"all non-major Go dependencies\",\n      \"groupSlug\": \"all-minor-patch-gomod\",\n      \"matchManagers\": [\n        \"gomod\"\n      ],\n      \"matchUpdateTypes\": [\n        \"minor\",\n        \"patch\",\n        \"pin\",\n        \"digest\"\n      ],\n      \"automerge\": true,\n      \"automergeType\": \"branch\",\n      \"matchPackageNames\": [\n        \"!go\"\n      ]\n    },\n    {\n      \"groupName\": \"all non-major GitHub Actions dependencies\",\n      \"groupSlug\": \"all-minor-patch-github-actions\",\n      \"matchManagers\": [\n        \"github-actions\"\n      ],\n      \"matchUpdateTypes\": [\n        \"minor\",\n        \"patch\",\n        \"pin\",\n        \"digest\"\n      ],\n      \"automerge\": true,\n      \"automergeType\": \"branch\"\n    },\n    {\n      \"matchUpdateTypes\": [\n        \"minor\",\n        \"patch\",\n        \"pin\",\n        \"digest\"\n      ],\n      \"automerge\": true,\n      \"automergeType\": \"branch\"\n    }\n  ]\n}\n"
  },
  {
    "path": "tail.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\ntype Tail struct {\n\tFinished     bool\n\tclosed       chan struct{}\n\tlogger       *Logger\n\tnamespace    string\n\tpod          string\n\tcontainer    string\n\tsinceSeconds int64\n\ttimestamps   bool\n}\n\n// NewTail creates new Tail object\nfunc NewTail(namespace, pod, container string, logger *Logger, sinceSeconds int64, timestamps bool) *Tail {\n\treturn &Tail{\n\t\tFinished:     false,\n\t\tclosed:       make(chan struct{}),\n\t\tlogger:       logger,\n\t\tnamespace:    namespace,\n\t\tpod:          pod,\n\t\tcontainer:    container,\n\t\tsinceSeconds: sinceSeconds,\n\t\ttimestamps:   timestamps,\n\t}\n}\n\n// Start starts Pod log streaming\nfunc (t *Tail) Start(ctx context.Context, clientset *kubernetes.Clientset) {\n\tt.logger.PrintPodDetected(t.pod, t.container)\n\n\tgo func() {\n\t\trs, err := clientset.CoreV1().Pods(t.namespace).GetLogs(t.pod, &v1.PodLogOptions{\n\t\t\tContainer:    t.container,\n\t\t\tFollow:       true,\n\t\t\tSinceSeconds: &t.sinceSeconds,\n\t\t\tTimestamps:   t.timestamps,\n\t\t}).Stream(ctx)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(os.Stderr, err)\n\t\t\treturn\n\t\t}\n\t\tdefer rs.Close()\n\n\t\tgo func() {\n\t\t\t<-t.closed\n\t\t\trs.Close()\n\t\t}()\n\n\t\tsc := bufio.NewScanner(rs)\n\n\t\tfor sc.Scan() {\n\t\t\tt.logger.PrintPodLog(t.pod, t.container, sc.Text(), t.timestamps)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\t<-ctx.Done()\n\t\tclose(t.closed)\n\t}()\n}\n\n// Finish finishes Pod log streaming with Pod completion\nfunc (t *Tail) Finish() {\n\tt.logger.PrintPodFinished(t.pod, t.container)\n\tt.Finished = true\n}\n\n// Delete finishes Pod log streaming with Pod deletion\nfunc (t *Tail) Delete() {\n\tt.logger.PrintPodDeleted(t.pod, t.container)\n\tclose(t.closed)\n}\n\ntype TailMap struct {\n\tmu sync.Mutex\n\n\tdata map[string]*Tail\n}\n\nfunc NewTailMap() *TailMap {\n\treturn &TailMap{\n\t\tdata: make(map[string]*Tail),\n\t}\n}\n\nfunc (m *TailMap) Get(k string) (*Tail, bool) {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\td, ok := m.data[k]\n\n\treturn d, ok\n}\n\nfunc (m *TailMap) Set(k string, v *Tail) {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tm.data[k] = v\n}\n\nfunc (m *TailMap) Delete(k string) {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tdelete(m.data, k)\n}\n"
  },
  {
    "path": "version.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n)\n\nvar (\n\tversion string\n\tcommit  string\n\tdate    string\n)\n\nfunc printVersion() {\n\tfmt.Printf(\"k8stail version: %s, commit: %s, build at: %s\\n\", version, commit, date)\n}\n"
  },
  {
    "path": "watch.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\n\t\"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n)\n\ntype Target struct {\n\tNamespace string\n\tPod       string\n\tContainer string\n}\n\n// NewTarget creates new Target object\nfunc NewTarget(namespace, pod, container string) *Target {\n\treturn &Target{\n\t\tNamespace: namespace,\n\t\tPod:       pod,\n\t\tContainer: container,\n\t}\n}\n\n// GetID returns target ID\nfunc (t *Target) GetID() string {\n\treturn t.Namespace + \"_\" + t.Pod + \"_\" + t.Container\n}\n\n// Watch starts and listens Kubernetes Pod events\nfunc Watch(ctx context.Context, watcher watch.Interface) (chan *Target, chan *Target, chan *Target) {\n\tadded := make(chan *Target)\n\tfinished := make(chan *Target)\n\tdeleted := make(chan *Target)\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase e := <-watcher.ResultChan():\n\t\t\t\tif e.Object == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tpod := e.Object.(*v1.Pod)\n\n\t\t\t\tswitch e.Type {\n\t\t\t\tcase watch.Added:\n\t\t\t\t\tif pod.Status.Phase != v1.PodRunning {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, container := range pod.Spec.Containers {\n\t\t\t\t\t\tadded <- NewTarget(pod.Namespace, pod.Name, container.Name)\n\t\t\t\t\t}\n\t\t\t\tcase watch.Modified:\n\t\t\t\t\tswitch pod.Status.Phase {\n\t\t\t\t\tcase v1.PodRunning:\n\t\t\t\t\t\tfor _, container := range pod.Spec.Containers {\n\t\t\t\t\t\t\tadded <- NewTarget(pod.Namespace, pod.Name, container.Name)\n\t\t\t\t\t\t}\n\t\t\t\t\tcase v1.PodSucceeded, v1.PodFailed:\n\t\t\t\t\t\tfor _, container := range pod.Spec.Containers {\n\t\t\t\t\t\t\tfinished <- NewTarget(pod.Namespace, pod.Name, container.Name)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tcase watch.Deleted:\n\t\t\t\t\tfor _, container := range pod.Spec.Containers {\n\t\t\t\t\t\tdeleted <- NewTarget(pod.Namespace, pod.Name, container.Name)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\tcase <-ctx.Done():\n\t\t\t\twatcher.Stop()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn added, finished, deleted\n}\n"
  }
]