Repository: yanc0/untrak
Branch: master
Commit: 163605dec1e1
Files: 19
Total size: 16.6 KB
Directory structure:
gitextract_7ejnjs7d/
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── config/
│ ├── loader.go
│ └── structs.go
├── example/
│ └── manifests/
│ ├── resources_in.yaml
│ └── resources_out.yaml
├── go.mod
├── go.sum
├── kubernetes/
│ ├── non_namespaced.go
│ └── structs.go
├── main.go
├── outputs/
│ ├── text.go
│ └── yaml.go
├── untrak.yaml
└── utils/
├── commands.go
└── strings.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# untrak binary
untrak
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
================================================
FILE: .travis.yml
================================================
language: go
go:
- 1.14.x
script:
- go get -v -d ./...
- GOOS=linux GOARCH=amd64 go build -o untrak-linux
- GOOS=darwin GOARCH=amd64 go build -o untrak-darwin
deploy:
provider: releases
api_key:
secure: u76RGeXFqXOBTOiEFChuXS7C1aaLl7vX8J39hOyla22LAyIeSOmOK6atwMfz6zq3cui9wo/bQ6Mke3p5Fzm1bhvA1srLo+Ma59JYEzsMvyDE0A7OZG7W8S+iJYEn/rlLGBzjAozg0TlmOMh8s/H05TyUEVOOqFwlEyb/O4H4Zg/ooJdNv0A+MLYGqgCiyyDv2tuMzY6GNeiBLrR69iAJfEjkrOfzC+dnVKEHi9K6OV1UEWX/63VHd2SfvJmG9vOL++3QH05ToMP0Geuek0fvQ0c5EYjAZYW4d4zw3dxXsHLwN7GRjQuos0uGXGqeHaSkYfSbtDQhLNV3FRjXlp8r4fhjhHcWQm4GO5tZItMMWVFwA8S1v+aiZ4B2NwrewwOxRzyBqtYGV16yklkHngOwsbU3596TKQeWv9v7sCw7f1N+uoAYmbyf7Si95WZnvkdIW1DvADg/32+dOcsLOKWFo1wlkoA44O8qOUetfHFd9DDApvd1n6XfvsWkejOdk8X02R/1XB3fCFRxONegIsVXjhlodBS0AzoF398goDfiDtj9TCdOwHUMIGFGZ/SARJRFwkCNkzY46B1/P4yH/vzrxIsALAuPDGUjqfrERtpl289M/0SNz4j7hhDgVYq+BLzipErDMmT+3l+RRTR9rKcHWN2NNm2zkDNoNRVvyiZtpp0=
file:
- untrak-linux
- untrak-darwin
skip_cleanup: true
on:
tags: true
all_branches: true
================================================
FILE: CHANGELOG.md
================================================
# Untrak Changelog
## v0.2.0 - 2020-10-22
Thanks to @almariah !
* Support building osx binaries on tags (#4)
* Compare only name and kind if the resourceIn is not namespaced (#4)
* Support flag to fail on untracked resources (#4)
* Add support for env variables to be substitute in args (#4)
## v0.1.1 - 2020-05-27
* Fix yaml parsing bug
## v0.1.0 - 2019-03-26
* Initial Release
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Yann Coleu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Untrak
Find untracked resources in Kubernetes cluster, garbage collect them.
[](https://travis-ci.org/yanc0/untrak)
## Why?
When you use `kubectl apply`, `kustomize build` or `helm template` for injecting manifests through you CI/CD pipeline, kubernetes doesn't know when an object has been deleted from your repo. Your resources are now **untracked** from your delivery process and they are still managed by your clusters.
Untrak is a tool made for finding and deleting these untracked files on your cluster.
## How it works?

Via a simple config file (`untrak.yaml`), this tool will internally execute commands that output YAMLs and find resources in your clusters that are not in your SCM anymore.
In a GitOps context, this is the tool you always dreamed of.
## Installation
Download latest version on [releases page](https://github.com/yanc0/untrak/releases)
- `chmod +x untrak`
- `sudo mv untrak /usr/local/bin`
- `untrak --help`
## Example
Put a `untrak.yaml` file in you SCM.
> Note: if you have multiple environments, you would need multiple untrak config files.
```yaml
# untrak.yaml
## git sources
in:
- cmd: "cat"
args: ["example/manifests/resources.yaml"]
## cluster manifests
out:
- cmd: "kubectl"
args: ["get", "cm,deploy,svc,ing", "-o", "yaml", "-n", "api"]
exclude:
- namespace
```
To show untracked resources in your cluster (out) simply launch `untrak` like so:
```
$ untrak -c untrak.yaml -o text
- api/ConfigMap/django-config-b4k42gm792
- api/ConfigMap/django-config-g55mctg456
- api/Ingress/my-ingress
```
If your manifests have the namespace set to non-namespaced resource, untrak will skip the namespace. A list of supported non-namespaced resource types that will be skipped are defined by default. If you have installed more non-namespaced resource types (eg., `CRDs`), you could add extra resource types to skip namespace in comparison:
```yaml
# untrak.yaml
## git sources
in:
...
## cluster manifests
out:
...
nonNamespaced:
- some_crd_type
```
You can use environment variables on command arguments (in/out):
```yaml
in:
- cmd: "cat"
args: ["example/$SOME_FILE_NAME"]
...
```
If you need to garbage collect them, you can change the output format to yaml and pipe the result in kubectl:
```
$ untrak -c untrak.yaml -o yaml | kubectl delete -f -
configmap "django-config-b4k42gm792" deleted
configmap "django-config-g55mctg456" deleted
ingress.extensions "my-ingress" deleted
```
If you want to fail on untracked resources (exit status 1), you can use `-fail`:
```
$ untrak -c untrak.yaml -fail
```
> **Caution**: please test this tool extensively before deleting resources. The software is provided "as is", without warranty of any kind.
================================================
FILE: config/loader.go
================================================
package config
import (
"io/ioutil"
yaml "gopkg.in/yaml.v2"
)
// Load untrak config from path
func Load(path string) (*Config, error) {
var cfg Config
content, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(content, &cfg)
if err != nil {
return nil, err
}
return &cfg, nil
}
================================================
FILE: config/structs.go
================================================
package config
type CommandConfig struct {
Cmd string `yaml:"cmd"`
Args []string `yaml:"args"`
}
type Config struct {
In []*CommandConfig `yaml:"in"`
Out []*CommandConfig `yaml:"out"`
Exclude []string `yaml:"exclude"`
NonNamespaced []string `yaml:"nonNamespaced"`
}
================================================
FILE: example/manifests/resources_in.yaml
================================================
---
apiVersion: v1
kind: Namespace
metadata:
name: app
namespace: app
---
apiVersion: v1
kind: Service
metadata:
labels:
app: django
env: dev
name: django
namespace: api
spec:
ports:
- name: http
port: 80
targetPort: 8000
selector:
app: django
env: dev
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: django
env: dev
name: django
namespace: api
spec:
replicas: 1
selector:
matchLabels:
app: django
env: dev
template:
metadata:
labels:
app: django
env: dev
spec:
containers:
- image: eu.gcr.io/example/django
imagePullPolicy: Always
livenessProbe:
failureThreshold: 20
httpGet:
path: /liveliness
port: 8000
initialDelaySeconds: 10
periodSeconds: 3
timeoutSeconds: 5
name: django
readinessProbe:
failureThreshold: 1
httpGet:
path: /readiness
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 5
---
================================================
FILE: example/manifests/resources_out.yaml
================================================
---
apiVersion: v1
kind: Service
metadata:
labels:
app: django
env: dev
name: django
namespace: api
spec:
ports:
- name: http
port: 80
targetPort: 8000
selector:
app: django
env: dev
type: ClusterIP
---
---
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: django
env: dev
name: django
namespace: api
spec:
replicas: 1
selector:
matchLabels:
app: django
env: dev
template:
metadata:
labels:
app: django
env: dev
spec:
containers:
- image: eu.gcr.io/example/django
imagePullPolicy: Always
livenessProbe:
failureThreshold: 20
httpGet:
path: /liveliness
port: 8000
initialDelaySeconds: 10
periodSeconds: 3
timeoutSeconds: 5
name: django
readinessProbe:
failureThreshold: 1
httpGet:
path: /readiness
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 5
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
labels:
app: django
env: dev
name: django
namespace: api
spec:
rules:
- host: django.example.com
http:
paths:
- backend:
serviceName: django
servicePort: 80
path: /
---
apiVersion: v1
kind: Namespace
metadata:
name: app
================================================
FILE: go.mod
================================================
module github.com/yanc0/untrak
go 1.12
require gopkg.in/yaml.v2 v2.3.0
================================================
FILE: go.sum
================================================
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
================================================
FILE: kubernetes/non_namespaced.go
================================================
package kubernetes
var DefaultNonNamespacedResources = []string{
"componentstatuse",
"namespace",
"node",
"persistentvolume",
"mutatingwebhookconfiguration",
"validatingwebhookconfiguration",
"customresourcedefinition",
"apiservice",
"certificatesigningrequest",
"runtimeclass",
"podsecuritypolicy",
"clusterrolebinding",
"clusterrole",
"priorityclass",
"csidriver",
"csinode",
"storageclass",
"volumeattachment",
}
================================================
FILE: kubernetes/structs.go
================================================
package kubernetes
import (
"fmt"
)
// Metadata of kubernetes resource
type Metadata struct {
Name string `yaml:"name"`
Namespace string `yaml:"namespace,omitempty"`
}
// Resource is a minimal description of a kubernetes object
type Resource struct {
APIVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
Metadata *Metadata `yaml:"metadata"`
Items []*Resource `yaml:"items,omitempty"`
}
// ID of the resource
func (r *Resource) ID() string {
return fmt.Sprintf("%s/%s/%s",
r.Metadata.Namespace,
r.Kind,
r.Metadata.Name,
)
}
//Empty return true if resource was not correctly loaded
func (r *Resource) Empty() bool {
return r.APIVersion == "" ||
r.Kind == "" ||
r.Metadata == nil
}
================================================
FILE: main.go
================================================
package main
import (
"bytes"
"flag"
"io"
"log"
"os"
"os/exec"
"sync"
"github.com/yanc0/untrak/outputs"
"github.com/yanc0/untrak/utils"
yaml "gopkg.in/yaml.v2"
"github.com/yanc0/untrak/kubernetes"
"github.com/yanc0/untrak/config"
)
func main() {
// Flags, command line parameters
var cfgPathOpt = flag.String("config", "./untrak.yaml", "untrak Config Path")
var outputOpt = flag.String("o", "text", "Output format")
var failOpt = flag.Bool("fail", false, "Fail on untracked resources")
flag.Parse()
var wg sync.WaitGroup
var resourcesIn []*kubernetes.Resource
var resourcesOut []*kubernetes.Resource
// Config Load
cfg, err := config.Load(*cfgPathOpt)
if err != nil {
log.Printf("[ERR] Cannot load %s file: %v\n", *cfgPathOpt, err)
os.Exit(1)
}
cfg.NonNamespaced = append(cfg.NonNamespaced, kubernetes.DefaultNonNamespacedResources...)
wg.Add(1)
go func() {
defer wg.Done()
resourcesIn, err = getKubernetesResources(cfg.In)
if err != nil {
log.Printf("[ERR] Failed to get Kubernetes resources (in): %v\n", err)
os.Exit(1)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
resourcesOut, err = getKubernetesResources(cfg.Out)
if err != nil {
log.Printf("[ERR] Failed to get Kubernetes resources (out): %v\n", err)
os.Exit(1)
}
}()
wg.Wait()
untrackedResources := listUntrackedResources(resourcesIn, resourcesOut, cfg.Exclude, cfg.NonNamespaced)
switch {
case *outputOpt == "text":
outputs.Text(untrackedResources)
case *outputOpt == "yaml":
outputs.YAML(untrackedResources)
default:
outputs.Text(untrackedResources)
}
if len(untrackedResources) > 0 && *failOpt {
os.Exit(1)
}
}
func getKubernetesResources(cfgs []*config.CommandConfig) ([]*kubernetes.Resource, error) {
const yamlSeparator = "---\n"
var resources []*kubernetes.Resource
var wg sync.WaitGroup
var mutex = &sync.Mutex{}
for _, cfg := range cfgs {
wg.Add(1)
go func(cmd string, args ...string) {
defer wg.Done()
// substitute env variables if any has been set
for i, _ := range args {
args[i] = os.ExpandEnv(args[i])
}
c := exec.Command(cmd, args...)
var outb, errb bytes.Buffer
c.Stdout = &outb
c.Stderr = &errb
err := c.Run()
if err != nil {
log.Fatal(err, errb.String())
}
stdoutDec := yaml.NewDecoder(&outb)
for {
tempResource := &kubernetes.Resource{}
err := stdoutDec.Decode(tempResource)
if err != nil && err != io.EOF {
log.Printf("[ERR] Failed to decode yaml stream: %s\n", err.Error())
os.Exit(1)
}
if err == io.EOF {
break
}
if tempResource.Kind == "List" {
mutex.Lock()
resources = append(resources, tempResource.Items...)
mutex.Unlock()
continue
}
// Resource can be empty if yaml file has return lines, separators or comments
// for example:
// # empty resource
// ---
// ---
// YAML decoder consider these lines valid but resource will be uninitialized
if !tempResource.Empty() {
mutex.Lock()
resources = append(resources, tempResource)
mutex.Unlock()
}
}
}(cfg.Cmd, cfg.Args...)
}
wg.Wait()
return resources, nil
}
func listUntrackedResources(in []*kubernetes.Resource, out []*kubernetes.Resource, kindExclude []string, nonNamespaced []string) []*kubernetes.Resource {
var untrackedResources []*kubernetes.Resource
for _, resourceOut := range out {
// Resource is in the exlude list, skip it
if utils.StringInListCaseInsensitive(kindExclude, resourceOut.Kind) {
continue
}
found := false
for _, resourceIn := range in {
// If input resource is not namespaced, compare only kind and Name
if utils.StringInListCaseInsensitive(nonNamespaced, resourceIn.Kind) {
if resourceOut.Kind == resourceIn.Kind && resourceOut.Metadata.Name == resourceIn.Metadata.Name {
found = true
break
}
}
// If resource has been found in both IN an OUT, there is nothing to do
if resourceOut.ID() == resourceIn.ID() {
found = true
break
}
}
// If resource OUT is not found in IN, it is untracked
if !found {
untrackedResources = append(untrackedResources, resourceOut)
}
}
return untrackedResources
}
================================================
FILE: outputs/text.go
================================================
package outputs
import (
"fmt"
"github.com/yanc0/untrak/kubernetes"
)
// Text output resources as text
func Text(resources []*kubernetes.Resource) {
var output string
for _, r := range resources {
out := r.ID()
output += fmt.Sprintf("- %s\n", out)
}
fmt.Println(output)
}
================================================
FILE: outputs/yaml.go
================================================
package outputs
import (
"fmt"
"github.com/yanc0/untrak/kubernetes"
yaml "gopkg.in/yaml.v2"
)
// YAML outputs resources as YAML
func YAML(resources []*kubernetes.Resource) {
var output string
for _, r := range resources {
out, err := yaml.Marshal(r)
if err != nil {
panic(err)
}
output += fmt.Sprintf("---\n%s\n", string(out))
}
fmt.Printf("%s", output)
}
================================================
FILE: untrak.yaml
================================================
---
# Untrak configuration
# All commands must produce YAML output on stdout either as:
# - Kind: List - List Kubernetes resource type(from kubectl get stdout)
# - Concatenated YAML with "---" separator
###
# Kubernetes resources from your versionned controlled configuration
in:
- cmd: "cat"
args: ["example/manifests/resources_in.yaml"]
out:
- cmd: "cat"
args: ["example/manifests/resources_out.yaml"]
# Kubernetes resources on your cluster
# check only configmaps, deployments, services and ingresses in api namespace
# out:
# - cmd: "kubectl"
# args: ["get", "cm,deploy,svc,ing", "-o", "yaml", "-n", "api"]
# You can use environment variables on command args
# out:
# - cmd: "cat"
# args: ["example/$SOME_FILE_NAME"]
# You can exclude some resource type from the comparison
exclude:
- namespace
- secret
- configmap
# Declare non-namespaced resource types to be considered in resource comparison.
# There are some defined resource types by default by default like namespace,
# node, clusterrole, etc.
# nonNamespaced:
# - some_crd_type
================================================
FILE: utils/commands.go
================================================
package utils
import (
"bytes"
"os/exec"
)
// Exec exec the command + args and returns stdout and stderr
func Exec(cmd string, args ...string) ([]byte, []byte, error) {
c := exec.Command(cmd, args...)
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
c.Stdout = stdout
c.Stderr = stderr
err := c.Run()
if err != nil {
return stdout.Bytes(), stderr.Bytes(), err
}
return stdout.Bytes(), stderr.Bytes(), nil
}
================================================
FILE: utils/strings.go
================================================
package utils
import (
"strings"
)
// StringInListCaseInsensitive return true if str is in the list (case insensitive)
func StringInListCaseInsensitive(list []string, str string) bool {
for _, s := range list {
if strings.ToLower(s) == strings.ToLower(str) {
return true
}
}
return false
}
gitextract_7ejnjs7d/
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── config/
│ ├── loader.go
│ └── structs.go
├── example/
│ └── manifests/
│ ├── resources_in.yaml
│ └── resources_out.yaml
├── go.mod
├── go.sum
├── kubernetes/
│ ├── non_namespaced.go
│ └── structs.go
├── main.go
├── outputs/
│ ├── text.go
│ └── yaml.go
├── untrak.yaml
└── utils/
├── commands.go
└── strings.go
SYMBOL INDEX (14 symbols across 8 files)
FILE: config/loader.go
function Load (line 10) | func Load(path string) (*Config, error) {
FILE: config/structs.go
type CommandConfig (line 3) | type CommandConfig struct
type Config (line 8) | type Config struct
FILE: kubernetes/structs.go
type Metadata (line 8) | type Metadata struct
type Resource (line 14) | type Resource struct
method ID (line 22) | func (r *Resource) ID() string {
method Empty (line 31) | func (r *Resource) Empty() bool {
FILE: main.go
function main (line 21) | func main() {
function getKubernetesResources (line 78) | func getKubernetesResources(cfgs []*config.CommandConfig) ([]*kubernetes...
function listUntrackedResources (line 138) | func listUntrackedResources(in []*kubernetes.Resource, out []*kubernetes...
FILE: outputs/text.go
function Text (line 10) | func Text(resources []*kubernetes.Resource) {
FILE: outputs/yaml.go
function YAML (line 11) | func YAML(resources []*kubernetes.Resource) {
FILE: utils/commands.go
function Exec (line 9) | func Exec(cmd string, args ...string) ([]byte, []byte, error) {
FILE: utils/strings.go
function StringInListCaseInsensitive (line 8) | func StringInListCaseInsensitive(list []string, str string) bool {
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (19K chars).
[
{
"path": ".gitignore",
"chars": 216,
"preview": "# untrak binary\nuntrak\n\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `"
},
{
"path": ".travis.yml",
"chars": 1014,
"preview": "language: go\ngo:\n- 1.14.x\nscript:\n - go get -v -d ./...\n - GOOS=linux GOARCH=amd64 go build -o untrak-linux\n - GOOS=d"
},
{
"path": "CHANGELOG.md",
"chars": 382,
"preview": "# Untrak Changelog\n\n## v0.2.0 - 2020-10-22\nThanks to @almariah !\n\n* Support building osx binaries on tags (#4)\n* Compare"
},
{
"path": "LICENSE",
"chars": 1067,
"preview": "MIT License\n\nCopyright (c) 2019 Yann Coleu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
},
{
"path": "README.md",
"chars": 2814,
"preview": "# Untrak\nFind untracked resources in Kubernetes cluster, garbage collect them.\n\n[\n\n// Load untrak config from path\nfunc Load(path string"
},
{
"path": "config/structs.go",
"chars": 320,
"preview": "package config\n\ntype CommandConfig struct {\n\tCmd string `yaml:\"cmd\"`\n\tArgs []string `yaml:\"args\"`\n}\n\ntype Config stru"
},
{
"path": "example/manifests/resources_in.yaml",
"chars": 1145,
"preview": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n name: app\n namespace: app\n---\napiVersion: v1\nkind: Service\nmetadata:\n l"
},
{
"path": "example/manifests/resources_out.yaml",
"chars": 1472,
"preview": "---\napiVersion: v1\nkind: Service\nmetadata:\n labels:\n app: django\n env: dev\n name: django\n namespace: api\nspec:\n"
},
{
"path": "go.mod",
"chars": 73,
"preview": "module github.com/yanc0/untrak\n\ngo 1.12\n\nrequire gopkg.in/yaml.v2 v2.3.0\n"
},
{
"path": "go.sum",
"chars": 360,
"preview": "gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v"
},
{
"path": "kubernetes/non_namespaced.go",
"chars": 434,
"preview": "package kubernetes\n\nvar DefaultNonNamespacedResources = []string{\n\t\"componentstatuse\",\n\t\"namespace\",\n\t\"node\",\n\t\"persiste"
},
{
"path": "kubernetes/structs.go",
"chars": 744,
"preview": "package kubernetes\n\nimport (\n\t\"fmt\"\n)\n\n// Metadata of kubernetes resource\ntype Metadata struct {\n\tName string `yaml"
},
{
"path": "main.go",
"chars": 4199,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"sync\"\n\n\t\"github.com/yanc0/untrak/outputs\"\n\t\"gith"
},
{
"path": "outputs/text.go",
"chars": 285,
"preview": "package outputs\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/yanc0/untrak/kubernetes\"\n)\n\n// Text output resources as text\nfunc Text(re"
},
{
"path": "outputs/yaml.go",
"chars": 377,
"preview": "package outputs\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/yanc0/untrak/kubernetes\"\n\tyaml \"gopkg.in/yaml.v2\"\n)\n\n// YAML outputs reso"
},
{
"path": "untrak.yaml",
"chars": 1055,
"preview": "---\n# Untrak configuration\n# All commands must produce YAML output on stdout either as:\n# - Kind: List - List Kubernete"
},
{
"path": "utils/commands.go",
"chars": 427,
"preview": "package utils\n\nimport (\n\t\"bytes\"\n\t\"os/exec\"\n)\n\n// Exec exec the command + args and returns stdout and stderr\nfunc Exec(c"
},
{
"path": "utils/strings.go",
"chars": 303,
"preview": "package utils\n\nimport (\n\t\"strings\"\n)\n\n// StringInListCaseInsensitive return true if str is in the list (case insensitive"
}
]
About this extraction
This page contains the full source code of the yanc0/untrak GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (16.6 KB), approximately 5.4k tokens, and a symbol index with 14 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.