diff --git a/go.mod b/go.mod index 13f769fc..2611aba5 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,11 @@ module github.com/fntlnz/kubectl-trace require ( + github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect github.com/davecgh/go-spew v1.1.1 - github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect + github.com/docker/distribution v2.6.2+incompatible // indirect + github.com/docker/docker v0.7.3-0.20181124105010-0b7cb16dde4a // indirect + github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 // indirect github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a // indirect github.com/evanphx/json-patch v4.1.0+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect @@ -11,22 +14,26 @@ require ( github.com/gogo/protobuf v1.1.1 // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect + github.com/google/go-cmp v0.2.0 // indirect github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect github.com/googleapis/gnostic v0.2.0 // indirect github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/json-iterator/go v1.1.5 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/onsi/ginkgo v1.7.0 // indirect + github.com/onsi/gomega v1.4.3 // indirect github.com/pborman/uuid v1.2.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.8.0 // indirect + github.com/sirupsen/logrus v1.2.0 // indirect github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.3 - go.uber.org/atomic v1.3.2 // indirect - go.uber.org/multierr v1.1.0 // indirect - go.uber.org/zap v1.9.1 + github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 // indirect golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba // indirect @@ -34,14 +41,18 @@ require ( golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect google.golang.org/appengine v1.3.0 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gotest.tools v2.2.0+incompatible // indirect k8s.io/api v0.0.0-20181121071145-b7bd5f2d334c - k8s.io/apimachinery v0.0.0-20181121071008-d4f83ca2e260 + k8s.io/apiextensions-apiserver v0.0.0-20181121072900-e8a638592964 // indirect + k8s.io/apimachinery v0.0.0-20180913025736-6dd46049f395 + k8s.io/apiserver v0.0.0-20181121231732-e3c8fa95bba5 // indirect k8s.io/cli-runtime v0.0.0-20181121073402-2f0d1d0a58f2 k8s.io/client-go v9.0.0+incompatible k8s.io/klog v0.1.0 // indirect k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be // indirect k8s.io/kubernetes v1.12.2 - k8s.io/utils v0.0.0-20181115163542-0d26856f57b3 + k8s.io/utils v0.0.0-20181115163542-0d26856f57b3 // indirect sigs.k8s.io/yaml v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index f2c5f8be..582286bd 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,25 @@ +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= -github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docker/distribution v2.6.2+incompatible h1:4FI6af79dfCS/CYb+RRtkSHw3q1L/bnDjG1PcPZtQhM= +github.com/docker/distribution v2.6.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20181124105010-0b7cb16dde4a h1:YAO+QsfAAW1ZgMI6GziquQGKUS8PhvGBEyeIf4maW1k= +github.com/docker/docker v0.7.3-0.20181124105010-0b7cb16dde4a/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a h1:A4wNiqeKqU56ZhtnzJCTyPZ1+cyu8jKtIchQ3TtxHgw= github.com/elazarl/goproxy v0.0.0-20181111060418-2ce16c963a8a/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc= github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= @@ -30,6 +38,8 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= @@ -38,18 +48,34 @@ github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhp github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM= github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= @@ -58,28 +84,32 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 h1:4bT0pPowCpQImewr+BjzfUKcuFW+KVyB8d1OF3b6oTI= +github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba h1:YDkOrzGLLYybtuP6ZgebnO4OWYEYVMFSniazXsxrFN8= golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= @@ -88,25 +118,36 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuA golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible h1:y0IMTfclpMdsdIbr6uwmJn5/WZ7vFuObxDMdrylFM3A= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= k8s.io/api v0.0.0-20181121071145-b7bd5f2d334c h1:aSW17ws1n3Y/gxcAggEFSs+UJlzpE3+stTPLQSiVEno= k8s.io/api v0.0.0-20181121071145-b7bd5f2d334c/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/apimachinery v0.0.0-20181121071008-d4f83ca2e260 h1:aWwjpwRiIZglT1lHFRE7PImhvuV1saC+OW34xd8gNr0= -k8s.io/apimachinery v0.0.0-20181121071008-d4f83ca2e260/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apiextensions-apiserver v0.0.0-20181121072900-e8a638592964 h1:xXmxmtvxdq31qsBaNCZtQ28d9pu01EWUbXDQmMmCM6Q= +k8s.io/apiextensions-apiserver v0.0.0-20181121072900-e8a638592964/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= +k8s.io/apimachinery v0.0.0-20180913025736-6dd46049f395 h1:X+c9tYTDc9Pmt+Z1YSMqmUTCYf13VYe1u+ZwzjgpK0M= +k8s.io/apimachinery v0.0.0-20180913025736-6dd46049f395/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apiserver v0.0.0-20181121231732-e3c8fa95bba5 h1:l055lCDDjCXSb3HIk0E0IJaorhEUpvkOgSqXlyBwtKI= +k8s.io/apiserver v0.0.0-20181121231732-e3c8fa95bba5/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= k8s.io/cli-runtime v0.0.0-20181121073402-2f0d1d0a58f2 h1:0tWjdH70/BhNHxQ1cc0DEO6iogWpNoY4dYRzUtwg+/g= k8s.io/cli-runtime v0.0.0-20181121073402-2f0d1d0a58f2/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM= -k8s.io/client-go v9.0.0+incompatible h1:NXWpDuPFeVB5lYP1fTqJUtwigjtmRXJNtndnN53ldGI= +k8s.io/client-go v9.0.0+incompatible h1:2kqW3X2xQ9SbFvWZjGEHBLlWc1LG9JIJNXWkuqwdZ3A= k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk= k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be h1:aWEq4nbj7HRJ0mtKYjNSk/7X28Tl6TI6FeG8gKF+r7Q= k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/kubernetes v1.12.2 h1:J94sY8nziFyzMNhnbD5CiSZTo7yWytir0ia+vJuwYfc= +k8s.io/kubernetes v1.12.2 h1:JEj2cxR+5T31U4klP5hI5dxjr6udRIHm+4dzCEPk498= k8s.io/kubernetes v1.12.2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20181115163542-0d26856f57b3 h1:S3/Kq185JnolOEemhmDXXd23l2t4bX5hPQPQPADlF1E= k8s.io/utils v0.0.0-20181115163542-0d26856f57b3/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= diff --git a/pkg/attacher/attach.go b/pkg/attacher/attach.go index 26754fb2..9c35a5df 100644 --- a/pkg/attacher/attach.go +++ b/pkg/attacher/attach.go @@ -5,11 +5,10 @@ import ( "fmt" "io" "net/url" - "os" "time" "github.com/fntlnz/kubectl-trace/pkg/meta" - "go.uber.org/zap" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes/scheme" tcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" @@ -17,23 +16,24 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" ) type Attacher struct { + genericclioptions.IOStreams ctx context.Context CoreV1Client tcorev1.CoreV1Interface - logger *zap.Logger Config *restclient.Config } -func NewAttacher(client tcorev1.CoreV1Interface, config *restclient.Config) *Attacher { +func NewAttacher(client tcorev1.CoreV1Interface, config *restclient.Config, streams genericclioptions.IOStreams) *Attacher { return &Attacher{ CoreV1Client: client, Config: config, - logger: zap.NewNop(), ctx: context.TODO(), + IOStreams: streams, } } @@ -43,24 +43,21 @@ const ( invalidPodContainersSizeError = "unexpected number of containers in trace job pod" ) -func (a *Attacher) WithLogger(l *zap.Logger) { - if l == nil { - a.logger = zap.NewNop() - return - } - a.logger = l -} - func (a *Attacher) WithContext(c context.Context) { a.ctx = c } -func (a *Attacher) AttachJob(traceJobID string, namespace string) { +func (a *Attacher) AttachJob(traceJobID types.UID, namespace string) { a.Attach(fmt.Sprintf("%s=%s", meta.TraceIDLabelKey, traceJobID), namespace) } func (a *Attacher) Attach(selector, namespace string) { - go wait.PollImmediate(time.Second, 10*time.Second, func() (bool, error) { + go wait.ExponentialBackoff(wait.Backoff{ + Duration: time.Second * 1, + Factor: 2, + Jitter: 0, + Steps: 10, + }, func() (bool, error) { pl, err := a.CoreV1Client.Pods(namespace).List(metav1.ListOptions{ LabelSelector: selector, }) @@ -84,48 +81,64 @@ func (a *Attacher) Attach(selector, namespace string) { restClient := a.CoreV1Client.RESTClient().(*restclient.RESTClient) containerName := pod.Spec.Containers[0].Name - t, err := setupTTY(os.Stdout, os.Stdin) + t, err := setupTTY(a.IOStreams.Out, a.IOStreams.In) if err != nil { return false, err } - err = t.Safe(defaultAttachFunc(restClient, pod.Name, containerName, pod.Namespace, a.Config, t)) + ao := attach{ + restClient: restClient, + podName: pod.Name, + namespace: pod.Namespace, + containerName: containerName, + config: a.Config, + tty: t, + } + err = t.Safe(ao.defaultAttachFunc()) if err != nil { - a.logger.Warn("attach retry", zap.Error(err)) + // on error, just send false so that the backoff mechanism can do a new tentative return false, nil } - return true, nil }) <-a.ctx.Done() } -func defaultAttachFunc(restClient *restclient.RESTClient, podName string, containerName string, namespace string, config *restclient.Config, t term.TTY) func() error { +type attach struct { + restClient *restclient.RESTClient + podName string + containerName string + namespace string + config *restclient.Config + tty term.TTY +} + +func (a attach) defaultAttachFunc() func() error { return func() error { - req := restClient.Post(). + req := a.restClient.Post(). Resource("pods"). - Name(podName). - Namespace(namespace). + Name(a.podName). + Namespace(a.namespace). SubResource("attach") req.VersionedParams(&corev1.PodAttachOptions{ - Container: containerName, + Container: a.containerName, Stdin: true, Stdout: true, - Stderr: true, - TTY: t.Raw, + Stderr: false, + TTY: a.tty.Raw, }, scheme.ParameterCodec) att := &defaultRemoteAttach{} // since the TTY is always in raw mode when attaching do a fake resize // of the screen so that it will be redrawn during attach and detach - tsize := t.GetSize() + tsize := a.tty.GetSize() tsizeinc := *tsize tsizeinc.Height++ tsizeinc.Width++ - terminalSizeQueue := t.MonitorSize(&tsizeinc, tsize) - return att.Attach("POST", req.URL(), config, t.In, t.Out, os.Stderr, t.Raw, terminalSizeQueue) + terminalSizeQueue := a.tty.MonitorSize(&tsizeinc, tsize) + return att.Attach("POST", req.URL(), a.config, a.tty.In, a.tty.Out, nil, a.tty.Raw, terminalSizeQueue) } } diff --git a/pkg/cmd/delete.go b/pkg/cmd/delete.go index 95d4cfc6..741dce7c 100644 --- a/pkg/cmd/delete.go +++ b/pkg/cmd/delete.go @@ -7,7 +7,6 @@ import ( "github.com/fntlnz/kubectl-trace/pkg/factory" "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" - // "k8s.io/kubernetes/pkg/kubectl/util/templates" ) var ( @@ -16,9 +15,12 @@ var ( ...` deleteExamples = ` - # Delete a specific bpftrace program + # Delete a specific bpftrace program by ID %[1]s trace delete k656ee75a-ee3c-11e8-9e7a-8c164500a77e + # Delete a specific bpftrace program by name + %[1]s trace delete kubectl-trace-1bb3ae39-efe8-11e8-9f29-8c164500a77e + # Delete all bpftrace programs in a specific namespace %[1]s trace delete -n myns"` ) @@ -26,6 +28,9 @@ var ( // DeleteOptions ... type DeleteOptions struct { genericclioptions.IOStreams + traceID string + traceName string + namespace string } // NewDeleteOptions provides an instance of DeleteOptions with default values. @@ -35,6 +40,39 @@ func NewDeleteOptions(streams genericclioptions.IOStreams) *DeleteOptions { } } +func (o *DeleteOptions) Validate(cmd *cobra.Command, args []string) error { + switch len(args) { + case 1: + o.traceID = args[0] + break + default: + return fmt.Errorf(requiredArgErrString) + } + + return nil +} + +func (o *DeleteOptions) Complete(factory factory.Factory, cmd *cobra.Command, args []string) error { + // Prepare namespace + var err error + o.namespace, _, err = factory.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + //// Prepare client + //clientConfig, err := factory.ToRESTConfig() + //if err != nil { + //return err + //} + //o.client, err = batchv1client.NewForConfig(clientConfig) + //if err != nil { + //return err + //} + + return nil +} + // NewDeleteCommand provides the delete command wrapping DeleteOptions. func NewDeleteCommand(factory factory.Factory, streams genericclioptions.IOStreams) *cobra.Command { o := NewDeleteOptions(streams) @@ -44,6 +82,9 @@ func NewDeleteCommand(factory factory.Factory, streams genericclioptions.IOStrea Short: deleteShort, Long: deleteLong, // Wrap with templates.LongDesc() Example: fmt.Sprintf(deleteExamples, "kubectl"), // Wrap with templates.Examples() + PreRunE: func(c *cobra.Command, args []string) error { + return o.Validate(c, args) + }, Run: func(c *cobra.Command, args []string) { fmt.Println("delete") spew.Dump(o) diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index 3f2ab3b0..9fa8df07 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -1,17 +1,23 @@ package cmd import ( + "context" "fmt" "io/ioutil" + "github.com/fntlnz/kubectl-trace/pkg/attacher" "github.com/fntlnz/kubectl-trace/pkg/factory" + "github.com/fntlnz/kubectl-trace/pkg/meta" + "github.com/fntlnz/kubectl-trace/pkg/signals" "github.com/fntlnz/kubectl-trace/pkg/tracejob" "github.com/spf13/cobra" "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/kubernetes/scheme" - batchv1client "k8s.io/client-go/kubernetes/typed/batch/v1" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" ) var ( @@ -52,8 +58,11 @@ type RunOptions struct { eval string program string resourceArg string + attach bool + + nodeName string - client batchv1client.BatchV1Interface + clientConfig *rest.Config } // NewRunOptions provides an instance of RunOptions with default values. @@ -68,7 +77,7 @@ func NewRunCommand(factory factory.Factory, streams genericclioptions.IOStreams) o := NewRunOptions(streams) cmd := &cobra.Command{ - Use: fmt.Sprintf("%s %s [-c CONTAINER]", runCommand, usageString), + Use: fmt.Sprintf("%s %s [-c CONTAINER] [--attach]", runCommand, usageString), Short: runShort, Long: runLong, // Wrap with templates.LongDesc() Example: fmt.Sprintf(runExamples, "kubectl"), // Wrap with templates.Examples() @@ -88,6 +97,7 @@ func NewRunCommand(factory factory.Factory, streams genericclioptions.IOStreams) } cmd.Flags().StringVarP(&o.container, "container", "c", o.container, "Specify the container") + cmd.Flags().BoolVarP(&o.attach, "attach", "a", o.attach, "Wheter or not to attach to the trace program once it is created") cmd.Flags().StringVarP(&o.eval, "eval", "e", "", "Literal string to be evaluated as a bpftrace program") cmd.Flags().StringVarP(&o.program, "program", "p", "", "File containing a bpftrace program") @@ -162,7 +172,7 @@ func (o *RunOptions) Complete(factory factory.Factory, cmd *cobra.Command, args // Check we got a pod or a node // isPod := false - switch obj.(type) { + switch v := obj.(type) { case *v1.Pod: // isPod = true // if len(o.container) == 0 { @@ -173,17 +183,14 @@ func (o *RunOptions) Complete(factory factory.Factory, cmd *cobra.Command, args return fmt.Errorf("running bpftrace programs against pods is not supported yet, see: https://github.com/fntlnz/kubectl-trace/issues/3") break case *v1.Node: + o.nodeName = v.GetName() break default: return fmt.Errorf("first argument must be %s", usageString) } // Prepare client - clientConfig, err := factory.ToRESTConfig() - if err != nil { - return err - } - o.client, err = batchv1client.NewForConfig(clientConfig) + o.clientConfig, err = factory.ToRESTConfig() if err != nil { return err } @@ -193,13 +200,44 @@ func (o *RunOptions) Complete(factory factory.Factory, cmd *cobra.Command, args // Run executes the run command. func (o *RunOptions) Run() error { - tj := tracejob.NewTraceJob(o.namespace, o.resourceArg, o.program) + juid := uuid.NewUUID() + jobsClient, err := batchv1client.NewForConfig(o.clientConfig) + if err != nil { + return err + } - _, err := o.client.Jobs(o.namespace).Create(tj.Object()) + coreClient, err := corev1client.NewForConfig(o.clientConfig) if err != nil { return err } - fmt.Fprintf(o.IOStreams.Out, "trace %s created\n", tj.ID()) + tc := &tracejob.TraceJobClient{ + JobClient: jobsClient.Jobs(o.namespace), + ConfigClient: coreClient.ConfigMaps(o.namespace), + } + + tj := tracejob.TraceJob{ + Name: fmt.Sprintf("%s%s", meta.ObjectNamePrefix, string(juid)), + Namespace: o.namespace, + ID: juid, + Hostname: o.nodeName, + Program: o.program, + } + + job, err := tc.CreateJob(tj) + if err != nil { + return err + } + + fmt.Fprintf(o.IOStreams.Out, "trace %s created\n", tj.ID) + + if o.attach { + ctx := context.Background() + ctx = signals.WithStandardSignals(ctx) + a := attacher.NewAttacher(coreClient, o.clientConfig, o.IOStreams) + a.WithContext(ctx) + a.AttachJob(tj.ID, job.Namespace) + } + return nil } diff --git a/pkg/meta/constants.go b/pkg/meta/constants.go index 62a51229..3f494bb5 100644 --- a/pkg/meta/constants.go +++ b/pkg/meta/constants.go @@ -7,4 +7,7 @@ const ( TraceIDLabelKey = "fntlnz.wtf/kubectl-trace-id" // TraceLabelKey is a meta to annotate objects created by this tool TraceLabelKey = "fntlnz.wtf/kubectl-trace" + + // ObjectNamePrefix is the prefix used for objects created by kubectl-trace + ObjectNamePrefix = "kubectl-trace-" ) diff --git a/pkg/meta/utils.go b/pkg/meta/utils.go new file mode 100644 index 00000000..0b0d923d --- /dev/null +++ b/pkg/meta/utils.go @@ -0,0 +1,13 @@ +package meta + +import ( + "strings" +) + +// IsObjectName return true if the provived string +func IsObjectName(name string) bool { + if strings.Compare(ObjectNamePrefix, name) == 0 { + return false + } + return strings.HasPrefix(name, ObjectNamePrefix) +} diff --git a/pkg/meta/utils_test.go b/pkg/meta/utils_test.go new file mode 100644 index 00000000..875a2360 --- /dev/null +++ b/pkg/meta/utils_test.go @@ -0,0 +1,49 @@ +package meta + +import "testing" + +func TestIsObjectName(t *testing.T) { + tests := []struct { + name string + objectName string + want bool + }{ + { + name: "string with the right prefix", + objectName: "kubectl-trace-1bb3ae39-efe8-11e8-9f29-8c164500a77e", + want: true, + }, + { + name: "string with another prefix", + objectName: "ekubectl-trace-1bb3ae39-efe8-11e8-9f29-8c164500a77e", + want: false, + }, + { + name: "just an uuid", + objectName: "1bb3ae39-efe8-11e8-9f29-8c164500a77e", + want: false, + }, + { + name: "empty string", + objectName: "", + want: false, + }, + { + name: "just the prefix", + objectName: "kubectl-trace", + want: false, + }, + { + name: "just the prefix and a dash", + objectName: "kubectl-trace-", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := IsObjectName(tt.objectName); got != tt.want { + t.Errorf("IsObjectName() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/tracejob/job.go b/pkg/tracejob/job.go index 01afc298..a90ba44c 100644 --- a/pkg/tracejob/job.go +++ b/pkg/tracejob/job.go @@ -8,79 +8,179 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/uuid" - utilpointer "k8s.io/utils/pointer" + batchv1typed "k8s.io/client-go/kubernetes/typed/batch/v1" + corev1typed "k8s.io/client-go/kubernetes/typed/core/v1" ) -// TraceJob represents objects we can translate to kubernetes jobs for tracing purposes. -type TraceJob interface { - Object() *batchv1.Job - ID() string +type TraceJobClient struct { + JobClient batchv1typed.JobInterface + ConfigClient corev1typed.ConfigMapInterface } -// traceJob struct contains the info needed to create a trace job. -type traceJob struct { - id types.UID - name string - namespace string - hostname string - program string +type TraceJob struct { + Name string + ID types.UID + Namespace string + Hostname string + Program string } -// NewTraceJob creates a new TraceJob. -func NewTraceJob(namespace, hostname, program string) TraceJob { - id := uuid.NewUUID() - return &traceJob{ - id: id, - name: fmt.Sprintf("%s%s", meta.TracePrefix, id), - namespace: namespace, - hostname: hostname, - program: program, +type TraceJobFilter struct { + Name *string + ID *types.UID +} + +func (t *TraceJobClient) findJobsWithFilter(nf TraceJobFilter) ([]batchv1.Job, error) { + selectorOptions := metav1.ListOptions{} + + if nf.Name != nil { + selectorOptions = metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", meta.TraceLabelKey, *nf.Name), + } } + + if nf.ID != nil { + selectorOptions = metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", meta.TraceIDLabelKey, *nf.ID), + } + } + + jl, err := t.JobClient.List(selectorOptions) + + if err != nil { + return nil, err + } + return jl.Items, nil } -// ID returns the trace job identifier. -func (j *traceJob) ID() string { - return string(j.id) +func (t *TraceJobClient) findConfigMapsWithFilter(nf TraceJobFilter) ([]apiv1.ConfigMap, error) { + selectorOptions := metav1.ListOptions{} + + if nf.Name != nil { + selectorOptions = metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", meta.TraceLabelKey, *nf.Name), + } + } + + if nf.ID != nil { + selectorOptions = metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", meta.TraceIDLabelKey, *nf.ID), + } + } + + cm, err := t.ConfigClient.List(selectorOptions) + + if err != nil { + return nil, err + } + return cm.Items, nil +} + +func (t *TraceJobClient) GetJob(nf TraceJobFilter) ([]TraceJob, error) { + + jl, err := t.findJobsWithFilter(nf) + if err != nil { + return nil, err + } + tjobs := []TraceJob{} + + for _, j := range jl { + labels := j.GetLabels() + name, ok := labels[meta.TraceLabelKey] + if !ok { + name = "" + } + id, ok := labels[meta.TraceIDLabelKey] + if !ok { + id = "" + } + hostname, err := jobHostname(j) + if err != nil { + hostname = "" + } + tj := TraceJob{ + Name: name, + ID: types.UID(id), + Namespace: j.Namespace, + Hostname: hostname, + } + tjobs = append(tjobs, tj) + } + + return tjobs, nil +} + +func (t *TraceJobClient) DeleteJob(nf TraceJobFilter) error { + jl, err := t.findJobsWithFilter(nf) + if err != nil { + return err + } + + dp := metav1.DeletePropagationForeground + for _, j := range jl { + err := t.JobClient.Delete(j.Name, &metav1.DeleteOptions{ + PropagationPolicy: &dp, + }) + if err != nil { + return err + } + } + + cl, err := t.findConfigMapsWithFilter(nf) + + if err != nil { + return err + } + + for _, c := range cl { + err := t.ConfigClient.Delete(c.Name, nil) + if err != nil { + return err + } + } + return nil } -// Create setup a new Job for bpftrace program. -func (j *traceJob) Object() *batchv1.Job { - bpftraceCommand := []string{ +// todo(fntlnz): deal with programs that needs the user to send a signal to complete, +// like how the hist() function does +// Will likely need to allocate a TTY for this one thing. +func (t *TraceJobClient) CreateJob(nj TraceJob) (*batchv1.Job, error) { + bpfTraceCmd := []string{ "bpftrace", "/programs/program.bt", } commonMeta := metav1.ObjectMeta{ - Name: j.name, - Namespace: j.namespace, + Name: nj.Name, + Namespace: nj.Namespace, Labels: map[string]string{ - meta.TraceLabelKey: j.name, - meta.TraceIDLabelKey: j.ID(), + meta.TraceLabelKey: nj.Name, + meta.TraceIDLabelKey: string(nj.ID), }, Annotations: map[string]string{ - meta.TraceLabelKey: j.name, - meta.TraceIDLabelKey: j.ID(), + meta.TraceLabelKey: nj.Name, + meta.TraceIDLabelKey: string(nj.ID), }, } cm := &apiv1.ConfigMap{ ObjectMeta: commonMeta, Data: map[string]string{ - "program.bt": j.program, + "program.bt": nj.Program, }, } - return &batchv1.Job{ + job := &batchv1.Job{ ObjectMeta: commonMeta, Spec: batchv1.JobSpec{ - TTLSecondsAfterFinished: utilpointer.Int32Ptr(5), - Parallelism: utilpointer.Int32Ptr(1), - Completions: utilpointer.Int32Ptr(1), + TTLSecondsAfterFinished: int32Ptr(5), + Parallelism: int32Ptr(1), + Completions: int32Ptr(1), // This is why your tracing job is being killed after 100 seconds, - // someone should work on it to make it configurable and let it run indefinitely by default. - ActiveDeadlineSeconds: utilpointer.Int64Ptr(100), // TODO(fntlnz): allow canceling from kubectl and increase this, - BackoffLimit: utilpointer.Int32Ptr(1), + // someone should work on it to make it configurable and let it run + // indefinitely by default. + ActiveDeadlineSeconds: int64Ptr(100), // TODO(fntlnz): allow canceling from kubectl and increase this, + BackoffLimit: int32Ptr(1), Template: apiv1.PodTemplateSpec{ ObjectMeta: commonMeta, Spec: apiv1.PodSpec{ @@ -114,11 +214,11 @@ func (j *traceJob) Object() *batchv1.Job { }, Containers: []apiv1.Container{ apiv1.Container{ - Name: j.name, + Name: nj.Name, Image: "quay.io/fntlnz/kubectl-trace-bpftrace:master", //TODO(fntlnz): yes this should be configurable! + Command: bpfTraceCmd, TTY: true, Stdin: true, - Command: bpftraceCommand, VolumeMounts: []apiv1.VolumeMount{ apiv1.VolumeMount{ Name: "program", @@ -137,7 +237,7 @@ func (j *traceJob) Object() *batchv1.Job { }, }, SecurityContext: &apiv1.SecurityContext{ - Privileged: utilpointer.BoolPtr(true), + Privileged: boolPtr(true), }, }, }, @@ -151,9 +251,7 @@ func (j *traceJob) Object() *batchv1.Job { apiv1.NodeSelectorRequirement{ Key: "kubernetes.io/hostname", Operator: apiv1.NodeSelectorOpIn, - Values: []string{ - j.hostname, - }, + Values: []string{nj.Hostname}, }, }, }, @@ -165,166 +263,54 @@ func (j *traceJob) Object() *batchv1.Job { }, }, } + + if _, err := t.ConfigClient.Create(cm); err != nil { + return nil, err + } + return t.JobClient.Create(job) } -// type TraceJobClient struct { -// JobClient batchv1typed.JobInterface -// ConfigClient corev1typed.ConfigMapInterface -// } - -// type TraceJobFilter struct { -// Name *string -// ID *string -// } - -// func (t *TraceJobClient) findJobsWithFilter(nf TraceJobFilter) ([]batchv1.Job, error) { -// selectorOptions := metav1.ListOptions{} - -// if nf.Name != nil { -// selectorOptions = metav1.ListOptions{ -// LabelSelector: fmt.Sprintf("%s=%s", meta.TraceLabelKey, *nf.Name), -// } -// } - -// if nf.ID != nil { -// selectorOptions = metav1.ListOptions{ -// LabelSelector: fmt.Sprintf("%s=%s", meta.TraceIDLabelKey, *nf.ID), -// } -// } - -// jl, err := t.JobClient.List(selectorOptions) - -// if err != nil { -// return nil, err -// } -// return jl.Items, nil -// } - -// func (t *TraceJobClient) findConfigMapsWithFilter(nf TraceJobFilter) ([]apiv1.ConfigMap, error) { -// selectorOptions := metav1.ListOptions{} - -// if nf.Name != nil { -// selectorOptions = metav1.ListOptions{ -// LabelSelector: fmt.Sprintf("%s=%s", meta.TraceLabelKey, *nf.Name), -// } -// } - -// if nf.ID != nil { -// selectorOptions = metav1.ListOptions{ -// LabelSelector: fmt.Sprintf("%s=%s", meta.TraceIDLabelKey, *nf.ID), -// } -// } - -// cm, err := t.ConfigClient.List(selectorOptions) - -// if err != nil { -// return nil, err -// } -// return cm.Items, nil -// } - -// func (t *TraceJobClient) GetJob(nf TraceJobFilter) ([]TraceJob, error) { - -// jl, err := t.findJobsWithFilter(nf) -// if err != nil { -// return nil, err -// } -// tjobs := []TraceJob{} - -// for _, j := range jl { -// labels := j.GetLabels() -// name, ok := labels[meta.TraceLabelKey] -// if !ok { -// name = "" -// } -// id, ok := labels[meta.TraceIDLabelKey] -// if !ok { -// id = "" -// } -// hostname, err := jobHostname(j) -// if err != nil { -// hostname = "" -// } -// tj := TraceJob{ -// Name: name, -// ID: id, -// Namespace: j.Namespace, -// Hostname: hostname, -// } -// tjobs = append(tjobs, tj) -// } - -// return tjobs, nil -// } - -// func (t *TraceJobClient) DeleteJob(nf TraceJobFilter) error { -// jl, err := t.findJobsWithFilter(nf) -// if err != nil { -// return err -// } - -// dp := metav1.DeletePropagationForeground -// for _, j := range jl { -// err := t.JobClient.Delete(j.Name, &metav1.DeleteOptions{ -// PropagationPolicy: &dp, -// }) -// if err != nil { -// return err -// } -// } - -// cl, err := t.findConfigMapsWithFilter(nf) - -// if err != nil { -// return err -// } - -// for _, c := range cl { -// err := t.ConfigClient.Delete(c.Name, nil) -// if err != nil { -// return err -// } -// } -// return nil -// } - -// func jobHostname(j batchv1.Job) (string, error) { -// aff := j.Spec.Template.Spec.Affinity -// if aff == nil { -// return "", fmt.Errorf("affinity not found for job") -// } - -// nodeAff := aff.NodeAffinity - -// if nodeAff == nil { -// return "", fmt.Errorf("node affinity not found for job") -// } - -// requiredScheduling := nodeAff.RequiredDuringSchedulingIgnoredDuringExecution - -// if requiredScheduling == nil { -// return "", fmt.Errorf("node affinity RequiredDuringSchedulingIgnoredDuringExecution not found for job") -// } -// nst := requiredScheduling.NodeSelectorTerms -// if len(nst) == 0 { -// return "", fmt.Errorf("node selector terms are empty in node affinity for job") -// } - -// me := nst[0].MatchExpressions - -// if len(me) == 0 { -// return "", fmt.Errorf("node selector terms match expressions are empty in node affinity for job") -// } - -// for _, v := range me { -// if v.Key == "kubernetes.io/hostname" { -// if len(v.Values) == 0 { -// return "", fmt.Errorf("hostname affinity found but no values in it for job") -// } - -// return v.Values[0], nil -// } -// } - -// return "", fmt.Errorf("hostname not found for job") -// } +func int32Ptr(i int32) *int32 { return &i } +func int64Ptr(i int64) *int64 { return &i } +func boolPtr(b bool) *bool { return &b } + +func jobHostname(j batchv1.Job) (string, error) { + aff := j.Spec.Template.Spec.Affinity + if aff == nil { + return "", fmt.Errorf("affinity not found for job") + } + + nodeAff := aff.NodeAffinity + + if nodeAff == nil { + return "", fmt.Errorf("node affinity not found for job") + } + + requiredScheduling := nodeAff.RequiredDuringSchedulingIgnoredDuringExecution + + if requiredScheduling == nil { + return "", fmt.Errorf("node affinity RequiredDuringSchedulingIgnoredDuringExecution not found for job") + } + nst := requiredScheduling.NodeSelectorTerms + if len(nst) == 0 { + return "", fmt.Errorf("node selector terms are empty in node affinity for job") + } + + me := nst[0].MatchExpressions + + if len(me) == 0 { + return "", fmt.Errorf("node selector terms match expressions are empty in node affinity for job") + } + + for _, v := range me { + if v.Key == "kubernetes.io/hostname" { + if len(v.Values) == 0 { + return "", fmt.Errorf("hostname affinity found but no values in it for job") + } + + return v.Values[0], nil + } + } + + return "", fmt.Errorf("hostname not found for job") +}