diff --git a/go.mod b/go.mod index 76a983e3..7d9d1489 100644 --- a/go.mod +++ b/go.mod @@ -18,14 +18,19 @@ require ( github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.11.1 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index dfc86217..4d7d6a77 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,7 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA= github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -60,6 +61,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -125,6 +127,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -136,8 +139,10 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -146,6 +151,14 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -159,6 +172,8 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989 h1:rq2/kILQnPtq5oL4+IAjgVOjh5e2yj2aaCYi7squEvI= +github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= @@ -172,9 +187,12 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -234,8 +252,11 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -286,6 +307,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -295,6 +317,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -325,6 +348,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -372,6 +397,7 @@ golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -386,6 +412,8 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc= +golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -483,9 +511,15 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/myks/application.go b/internal/myks/application.go index 85c9981a..92437782 100644 --- a/internal/myks/application.go +++ b/internal/myks/application.go @@ -1,16 +1,10 @@ package myks import ( - "bytes" "errors" - "fmt" + yaml "gopkg.in/yaml.v3" "os" "path/filepath" - "regexp" - "strings" - - "github.com/rs/zerolog/log" - yaml "gopkg.in/yaml.v3" ) type Application struct { @@ -23,6 +17,7 @@ type Application struct { // YTT data files yttDataFiles []string cached bool + yttPkgDirs []string } type HelmConfig struct { @@ -65,16 +60,22 @@ func (a *Application) Init() error { a.collectDataFiles() - dataYaml, err := renderDataYaml(append(a.e.g.extraYttPaths, a.yttDataFiles...)) + dataYaml, err := renderDataYaml(a.Name, append(a.e.g.extraYttPaths, a.yttDataFiles...)) if err != nil { return err } + type Cache struct { + Enabled bool + } + type YttPkg struct { + Dirs []string `yaml:"dirs"` + } + var applicationData struct { Application struct { - Cache struct { - Enabled bool - } + Cache Cache `yaml:"cache"` + YttPkg YttPkg `yaml:"yttPkg"` } } @@ -83,129 +84,7 @@ func (a *Application) Init() error { return err } a.cached = applicationData.Application.Cache.Enabled - - return nil -} - -func (a *Application) Sync() error { - if err := a.prepareSync(); err != nil { - if err == ErrNoVendirConfig { - return nil - } - return err - } - - if err := a.doSync(); err != nil { - return err - } - - return nil -} - -func (a *Application) Render() error { - - log.Debug().Strs("files", a.yttDataFiles).Msg("Collected ytt data files") - - // 2. Run built-in rendering steps: - // a. helm - // b. ytt - // c. global ytt - - outputYaml, err := a.runHelm() - if err != nil { - return err - } - - helmStepOutputFile := "" - if outputYaml != "" { - helmStepOutputFile, err = a.storeStepResult(outputYaml, "helm", 1) - if err != nil { - log.Error().Err(err).Msg("Failed to store helm step result") - return err - } - } - - outputYaml, err = a.runYtt(helmStepOutputFile) - if err != nil { - return err - } - - yttStepOutputFile, err := a.storeStepResult(outputYaml, "ytt", 2) - if err != nil { - log.Error().Err(err).Msg("Failed to store ytt step result") - return err - } - - outputYaml, err = a.runGlobalYtt(yttStepOutputFile) - if err != nil { - return err - } - - globalYttStepOutputFile, err := a.storeStepResult(outputYaml, "global.ytt", 3) - if err != nil { - log.Error().Err(err).Msg("Failed to store global ytt step result") - return err - } - - // 3. Run custom rendering steps: TODO - - // 4. Run kube-slice and format - - err = a.runSliceFormatStore(globalYttStepOutputFile) - if err != nil { - return err - } - - return nil -} - -func (a *Application) prepareSync() error { - // Collect ytt arguments following the following steps: - // 1. If exists, use the `apps//vendir` directory. - // 2. If exists, for every level of environments use `/_apps//vendir` directory. - - var yttFiles []string - - protoVendirDir := filepath.Join(a.Prototype, "vendir") - if _, err := os.Stat(protoVendirDir); err == nil { - yttFiles = append(yttFiles, protoVendirDir) - log.Debug().Str("dir", protoVendirDir).Msg("Using prototype vendir directory") - } - - appVendirDirs := a.e.collectBySubpath(filepath.Join("_apps", a.Name, "vendir")) - yttFiles = append(yttFiles, appVendirDirs...) - - if len(yttFiles) == 0 { - err := ErrNoVendirConfig - log.Warn().Err(err).Str("app", a.Name).Msg("") - return err - } - - vendirConfig, err := a.e.g.ytt(yttFiles) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to render vendir config") - return err - } - - if vendirConfig.Stdout == "" { - err = errors.New("Empty vendir config") - log.Warn().Err(err).Msg("") - return err - } - - vendirConfigFilePath := a.expandServicePath(a.e.g.VendirConfigFileName) - // Create directory if it does not exist - err = os.MkdirAll(filepath.Dir(vendirConfigFilePath), 0o750) - if err != nil { - log.Warn().Err(err).Msg("Unable to create directory for vendir config file") - return err - } - err = os.WriteFile(vendirConfigFilePath, []byte(vendirConfig.Stdout), 0o600) - if err != nil { - log.Warn().Err(err).Msg("Unable to write vendir config file") - return err - } - log.Debug().Str("file", vendirConfigFilePath).Msg("Wrote vendir config file") + a.yttPkgDirs = applicationData.Application.YttPkg.Dirs return nil } @@ -242,350 +121,3 @@ func (a *Application) collectDataFiles() { overrideDataFiles := a.e.collectBySubpath(filepath.Join("_apps", a.Name, a.e.g.ApplicationDataFileName)) a.yttDataFiles = append(a.yttDataFiles, overrideDataFiles...) } - -func (a *Application) runHelm() (string, error) { - chartDirs := a.getHelmChartDirs() - if len(chartDirs) == 0 { - log.Debug().Str("app", a.Name).Msg("No charts to process") - return "", nil - } - - helmConfig, err := a.getHelmConfig() - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to get helm config") - return "", err - } - - var commonHelmArgs []string - - // FIXME: move Namespace to a per-chart config - if helmConfig.Namespace == "" { - helmConfig.Namespace = a.e.g.NamespacePrefix + a.Name - } - commonHelmArgs = append(commonHelmArgs, "--namespace", helmConfig.Namespace) - - if helmConfig.KubeVersion != "" { - commonHelmArgs = append(commonHelmArgs, "--kube-version", helmConfig.KubeVersion) - } - - // FIXME: move IncludeCRDs to a per-chart config - if helmConfig.IncludeCRDs { - commonHelmArgs = append(commonHelmArgs, "--include-crds") - } - - var outputs []string - - for _, chartDir := range chartDirs { - chartName := filepath.Base(chartDir) - if err := a.prepareHelm(chartName); err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to prepare helm values") - return "", err - } - - // FIXME: replace a.Name with a name of the chart being processed - helmArgs := []string{ - "template", - "--skip-tests", - chartName, - chartDir, - } - - helmValuesFile := a.expandServicePath(a.getHelmValuesFileName(chartName)) - if _, err := os.Stat(helmValuesFile); err == nil { - helmArgs = append(helmArgs, "--values", helmValuesFile) - } else { - log.Debug().Str("app", a.Name).Str("chart", chartName).Msg("No helm values file") - } - - res, err := runCmd("helm", nil, append(helmArgs, commonHelmArgs...)) - if err != nil { - log.Warn().Err(err).Str("stdout", res.Stdout).Str("stderr", res.Stderr).Msg("Unable to run helm") - return "", err - } - - if res.Stdout == "" { - log.Warn().Str("app", a.Name).Str("chart", chartName).Msg("No helm output") - continue - } - - outputs = append(outputs, res.Stdout) - - } - - return strings.Join(outputs, "---\n"), nil -} - -func (a *Application) getHelmChartsDir() (string, error) { - chartsDir := a.expandPath(filepath.Join(a.e.g.VendorDirName, a.e.g.HelmChartsDirName)) - if _, err := os.Stat(chartsDir); err != nil { - if os.IsNotExist(err) { - log.Debug().Str("dir", chartsDir).Msg("Helm charts directory does not exist") - return "", nil - } - log.Warn().Err(err).Str("dir", chartsDir).Msg("Unable to stat helm charts directory") - return "", err - } - - return chartsDir, nil -} - -func (a *Application) getHelmChartDirs() []string { - chartsDir, err := a.getHelmChartsDir() - if err != nil || chartsDir == "" { - return []string{} - } - - var chartDirs []string - err = filepath.Walk(chartsDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - log.Warn().Err(err).Str("path", path).Msg("Unable to walk helm charts directory") - return err - } - - if info.IsDir() && path != chartsDir { - chartDirs = append(chartDirs, path) - return filepath.SkipDir - } - - return nil - }) - - if err != nil { - log.Warn().Err(err).Msg("Unable to walk helm charts directory") - return []string{} - } - - return chartDirs -} - -// prepareHelm generates values.yaml file from ytt data files and ytt templates -// from the `helm` directories of the prototype and the application. -func (a *Application) prepareHelm(chartName string) error { - helmValuesFileName := a.getHelmValuesFileName(chartName) - - var helmYttFiles []string - - prototypeHelmValues := filepath.Join(a.Prototype, helmValuesFileName) - if _, err := os.Stat(prototypeHelmValues); err == nil { - helmYttFiles = append(helmYttFiles, prototypeHelmValues) - } - - helmYttFiles = append(helmYttFiles, a.e.collectBySubpath(filepath.Join("_apps", a.Name, helmValuesFileName))...) - - if len(helmYttFiles) == 0 { - log.Debug().Str("app", a.Name).Str("chart", chartName).Msg("No helm values templates found, helm values file will not be generated") - return nil - } - - log.Debug().Strs("files", helmYttFiles).Str("app", a.Name).Str("chart", chartName).Msg("Collected helm values templates") - - helmValuesYamls, err := a.e.g.ytt(append(a.yttDataFiles, helmYttFiles...)) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to render helm values templates") - return err - } - - if helmValuesYamls.Stdout == "" { - log.Warn().Str("app", a.Name).Msg("Empty helm values") - return nil - } - - err = a.writeTempFile(helmValuesFileName, helmValuesYamls.Stdout) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to write helm values file") - return err - } - - helmValues, err := mergeValuesYaml(a.expandTempPath(helmValuesFileName)) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to render helm values") - return err - } - - if helmValues.Stdout == "" { - log.Warn().Str("app", a.Name).Msg("Empty helm values") - return nil - } - - return a.writeServiceFile(helmValuesFileName, helmValues.Stdout) -} - -func (a *Application) getHelmConfig() (HelmConfig, error) { - dataValuesYaml, err := a.e.g.ytt(a.yttDataFiles, "--data-values-inspect") - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to inspect data values") - return HelmConfig{}, err - } - - var helmConfig struct { - Helm HelmConfig - } - err = yaml.Unmarshal([]byte(dataValuesYaml.Stdout), &helmConfig) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to unmarshal data values") - return HelmConfig{}, err - } - - return helmConfig.Helm, nil -} - -func (a *Application) getHelmValuesFileName(chartName string) string { - return filepath.Join("helm", chartName+".yaml") -} - -func (a *Application) runYtt(previousStepFile string) (string, error) { - var yttFiles []string - - yttFiles = append(yttFiles, a.yttDataFiles...) - - if previousStepFile != "" { - yttFiles = append(yttFiles, previousStepFile) - } - - vendorYttDir := a.expandPath(filepath.Join(a.e.g.VendorDirName, a.e.g.YttStepDirName)) - if _, err := os.Stat(vendorYttDir); err == nil { - yttFiles = append(yttFiles, vendorYttDir) - } - - prototypeYttDir := filepath.Join(a.Prototype, a.e.g.YttStepDirName) - if _, err := os.Stat(prototypeYttDir); err == nil { - yttFiles = append(yttFiles, prototypeYttDir) - } - - yttFiles = append(yttFiles, a.e.collectBySubpath(filepath.Join("_apps", a.Name, a.e.g.YttStepDirName))...) - - if len(yttFiles) == 0 { - log.Debug().Str("app", a.Name).Msg("No ytt files found") - return "", nil - } - - log.Debug().Strs("files", yttFiles).Str("app", a.Name).Msg("Collected ytt files") - - yttOutput, err := a.e.g.ytt(yttFiles) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to render ytt files") - return "", err - } - - if yttOutput.Stdout == "" { - log.Warn().Str("app", a.Name).Msg("Empty ytt output") - return "", nil - } - - return yttOutput.Stdout, nil -} - -func (a *Application) runGlobalYtt(previousStepFile string) (string, error) { - var yttFiles []string - - yttFiles = append(yttFiles, a.yttDataFiles...) - - if previousStepFile != "" { - yttFiles = append(yttFiles, previousStepFile) - } - - yttFiles = append(yttFiles, a.e.collectBySubpath(filepath.Join("_env", a.e.g.YttStepDirName))...) - - if len(yttFiles) == 0 { - log.Debug().Str("app", a.Name).Msg("No ytt files found") - return "", nil - } - - log.Debug().Strs("files", yttFiles).Str("app", a.Name).Msg("Collected ytt files") - - yttOutput, err := a.e.g.ytt(yttFiles) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Msg("Unable to render ytt files") - return "", err - } - - if yttOutput.Stdout == "" { - log.Warn().Str("app", a.Name).Msg("Empty ytt output") - return "", nil - } - - return yttOutput.Stdout, nil -} - -func (a *Application) runSliceFormatStore(previousStepFile string) error { - data, err := os.ReadFile(filepath.Clean(previousStepFile)) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Str("file", previousStepFile).Msg("Unable to read previous step file") - return err - } - - destinationDir := filepath.Join(a.e.g.RootDir, a.e.g.RenderedDir, "envs", a.e.Id, a.Name) - - // Cleanup the destination directory before writing new files - err = os.RemoveAll(destinationDir) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Str("dir", destinationDir).Msg("Unable to remove destination directory") - return err - } - err = os.MkdirAll(destinationDir, os.ModePerm) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Str("dir", destinationDir).Msg("Unable to create destination directory") - return err - } - - // Split the document into individual YAML documents - rgx := regexp.MustCompile(`(?m)^---\n`) - documents := rgx.Split(string(data), -1) - - for _, document := range documents { - if document == "" { - continue - } - - var obj map[string]interface{} - err := yaml.Unmarshal([]byte(document), &obj) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Str("file", previousStepFile).Msg("Unable to unmarshal yaml") - return err - } - - var data bytes.Buffer - enc := yaml.NewEncoder(&data) - enc.SetIndent(2) - err = enc.Encode(obj) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Str("file", previousStepFile).Msg("Unable to marshal yaml") - return err - } - - fileName := genRenderedResourceFileName(obj) - filePath := filepath.Join(destinationDir, fileName) - // FIXME: If a file already exists, we should merge the two documents (probably). - // For now, we just overwrite the file and log a warning. - if _, err := os.Stat(filePath); err == nil { - log.Warn().Str("app", a.Name).Str("file", filePath).Msg("File already exists, check duplicated resources") - } - err = writeFile(filePath, data.Bytes()) - if err != nil { - log.Warn().Err(err).Str("app", a.Name).Str("file", filePath).Msg("Unable to write file") - return err - } - } - return nil -} - -// storeStepResult saves output of a step to a file in the application's temp directory. -// Returns path to the file or an error. -func (a *Application) storeStepResult(output string, stepName string, stepNumber uint) (string, error) { - fileName := filepath.Join("steps", fmt.Sprintf("%02d-%s.yaml", stepNumber, stepName)) - file := a.expandTempPath(fileName) - return file, a.writeTempFile(fileName, output) -} - -// Generates a file name for each document using kind and name if available -func genRenderedResourceFileName(resource map[string]interface{}) string { - kind := "NO_KIND" - if g, ok := resource["kind"]; ok { - kind = g.(string) - } - name := "NO_NAME" - if n, ok := resource["metadata"].(map[string]interface{})["name"]; ok { - name = n.(string) - } - return fmt.Sprintf("%s-%s.yaml", strings.ToLower(kind), strings.ToLower(name)) -} diff --git a/internal/myks/environment.go b/internal/myks/environment.go index 48363a0b..172a076c 100644 --- a/internal/myks/environment.go +++ b/internal/myks/environment.go @@ -88,7 +88,13 @@ func (e *Environment) Render() error { if !ok { return fmt.Errorf("Unable to cast item to *Application") } - return app.Render() + yamlTemplatingTools := []YamlTemplatingTool{ + &Helm{ident: "helm", app: app, additive: true}, + &YttPkg{ident: "ytt-pkg", app: app, additive: true}, + &Ytt{ident: "ytt", app: app, additive: false}, + &GlobalYtt{ident: "global-ytt", app: app, additive: false}, + } + return app.RenderAndSlice(yamlTemplatingTools) }) } @@ -101,7 +107,13 @@ func (e *Environment) SyncAndRender() error { if err := app.Sync(); err != nil { return err } - return app.Render() + yamlTemplatingTools := []YamlTemplatingTool{ + &Helm{ident: "helm", app: app, additive: true}, + &YttPkg{ident: "ytt-pkg", app: app, additive: true}, + &Ytt{ident: "ytt", app: app, additive: false}, + &GlobalYtt{ident: "global-ytt", app: app, additive: false}, + } + return app.RenderAndSlice(yamlTemplatingTools) }) } diff --git a/internal/myks/globe.go b/internal/myks/globe.go index bc0fd6fb..20e0e21f 100644 --- a/internal/myks/globe.go +++ b/internal/myks/globe.go @@ -71,6 +71,8 @@ type Globe struct { // Ytt library directory name YttLibraryDirName string `default:"lib" yaml:"yttLibraryDirName"` // Ytt step directory name + YttPkgStepDirName string `default:"ytt-pkg" yaml:"yttPkgStepDirName"` + // Ytt step directory name YttStepDirName string `default:"ytt" yaml:"yttStepDirName"` /// User input diff --git a/internal/myks/render.go b/internal/myks/render.go new file mode 100644 index 00000000..2813595c --- /dev/null +++ b/internal/myks/render.go @@ -0,0 +1,213 @@ +package myks + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/rs/zerolog/log" + yaml "gopkg.in/yaml.v3" +) + +type YamlTemplatingTool interface { + Render(previousStepOutputFile string) (string, error) + Ident() string + IsAdditive() bool +} + +func (a *Application) RenderAndSlice(yamlTemplatingTools []YamlTemplatingTool) error { + var lastStepOutputFile string + var err error + if lastStepOutputFile, err = a.Render(yamlTemplatingTools); err != nil { + log.Error().Str("app", a.Name).Str("env", a.e.Id).Err(err).Msg("Failed to render") + } + err = a.runSliceFormatStore(lastStepOutputFile) + if err != nil { + log.Error().Err(err).Msg("Failed to slice the output yaml") + return err + } + return nil +} + +func (a *Application) Render(yamlTemplatingTools []YamlTemplatingTool) (string, error) { + outputYaml := "" + lastStepOutputFile := "" + for _, yamlTool := range yamlTemplatingTools { + log.Debug().Str("app", a.Name).Str("env", a.e.Id).Msg("Rendering: " + yamlTool.Ident()) + stepOutputYaml, err := yamlTool.Render(lastStepOutputFile) + if err != nil { + log.Error().Err(err).Msg("Failed during render step: " + yamlTool.Ident()) + } + if yamlTool.IsAdditive() { + outputYaml = outputYaml + "\n---\n" + stepOutputYaml + } else { + outputYaml = stepOutputYaml + } + lastStepOutputFile, err = a.storeStepResult(outputYaml, yamlTool.Ident(), 1) + if err != nil { + log.Error().Str("app", a.Name).Err(err).Msg("Failed to store step result for: " + yamlTool.Ident()) + return "", err + } + } + return lastStepOutputFile, nil +} + +func (a *Application) runSliceFormatStore(previousStepFile string) error { + data, err := os.ReadFile(filepath.Clean(previousStepFile)) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Str("file", previousStepFile).Msg("Unable to read previous step file") + return err + } + + destinationDir := filepath.Join(a.e.g.RootDir, a.e.g.RenderedDir, "envs", a.e.Id, a.Name) + + // Cleanup the destination directory before writing new files + err = os.RemoveAll(destinationDir) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Str("dir", destinationDir).Msg("Unable to remove destination directory") + return err + } + err = os.MkdirAll(destinationDir, os.ModePerm) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Str("dir", destinationDir).Msg("Unable to create destination directory") + return err + } + + // Split the document into individual YAML documents + rgx := regexp.MustCompile(`(?m)^---\n`) + documents := rgx.Split(string(data), -1) + + for _, document := range documents { + if document == "" { + continue + } + + var obj map[string]interface{} + err := yaml.Unmarshal([]byte(document), &obj) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Str("file", previousStepFile).Msg("Unable to unmarshal yaml") + return err + } + + var data bytes.Buffer + enc := yaml.NewEncoder(&data) + enc.SetIndent(2) + err = enc.Encode(obj) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Str("file", previousStepFile).Msg("Unable to marshal yaml") + return err + } + + fileName := genRenderedResourceFileName(obj) + filePath := filepath.Join(destinationDir, fileName) + // FIXME: If a file already exists, we should merge the two documents (probably). + // For now, we just overwrite the file and log a warning. + if _, err := os.Stat(filePath); err == nil { + log.Warn().Str("app", a.Name).Str("file", filePath).Msg("File already exists, check duplicated resources") + } + err = writeFile(filePath, data.Bytes()) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Str("file", filePath).Msg("Unable to write file") + return err + } + } + return nil +} + +// storeStepResult saves output of a step to a file in the application's temp directory. +// Returns path to the file or an error. +func (a *Application) storeStepResult(output string, stepName string, stepNumber uint) (string, error) { + fileName := filepath.Join("steps", fmt.Sprintf("%02d-%s.yaml", stepNumber, stepName)) + file := a.expandTempPath(fileName) + return file, a.writeTempFile(fileName, output) +} + +// Generates a file name for each document using kind and name if available +func genRenderedResourceFileName(resource map[string]interface{}) string { + kind := "NO_KIND" + if g, ok := resource["kind"]; ok { + kind = g.(string) + } + name := "NO_NAME" + if n, ok := resource["metadata"]; ok { + metadata := n.(map[string]interface{}) + name = metadata["name"].(string) + } + return fmt.Sprintf("%s-%s.yaml", strings.ToLower(kind), strings.ToLower(name)) +} + +func (a *Application) getVendoredDir(dirname string) (string, error) { + resourceDir := a.expandPath(filepath.Join(a.e.g.VendorDirName, dirname)) + if _, err := os.Stat(resourceDir); err != nil { + if os.IsNotExist(err) { + log.Debug().Str("app", a.Name).Msg("Vendored directory directory does not exist: " + resourceDir) + return "", nil + } + + log.Warn().Err(err).Str("app", a.Name).Msg("Unable to stat helm charts directory: " + resourceDir) + return "", err + } + + return resourceDir, nil +} + +// prepareValuesFile generates values.yaml file from ytt data files and ytt templates +// from the `helm` or `ytt` directories of the prototype and the application. +func (a *Application) prepareValuesFile(dirName string, resourceName string) (string, error) { + + valuesFileName := filepath.Join(dirName, resourceName+".yaml") + + var valuesFiles []string + + prototypeValuesFile := filepath.Join(a.Prototype, valuesFileName) + if _, err := os.Stat(prototypeValuesFile); err == nil { + valuesFiles = append(valuesFiles, prototypeValuesFile) + } + + valuesFiles = append(valuesFiles, a.e.collectBySubpath(filepath.Join("_apps", a.Name, valuesFileName))...) + + if len(valuesFiles) == 0 { + log.Debug().Str("app", a.Name).Str("resource", resourceName).Msg("No values files found") + return "", nil + } + + log.Debug().Strs("files", valuesFiles).Str("app", a.Name).Str("resourceName", resourceName).Msg("Collected resource values templates") + + resourceValuesYaml, err := a.e.g.ytt(append(a.yttDataFiles, valuesFiles...)) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Msg("Unable to render resource values templates") + return "", err + } + + if resourceValuesYaml.Stdout == "" { + log.Warn().Str("app", a.Name).Msg("Empty resource values") + return "", nil + } + + err = a.writeTempFile(valuesFileName, resourceValuesYaml.Stdout) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Msg("Unable to write resource values file") + return "", err + } + + resourceValues, err := mergeValuesYaml(a.expandTempPath(valuesFileName)) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Msg("Unable to render resource values") + return "", err + } + + if resourceValues.Stdout == "" { + log.Warn().Str("app", a.Name).Msg("Empty resource values") + return "", nil + } + + err = a.writeServiceFile(valuesFileName, resourceValues.Stdout) + if err != nil { + return "", err + } + + return a.expandServicePath(valuesFileName), nil +} diff --git a/internal/myks/render_global_ytt.go b/internal/myks/render_global_ytt.go new file mode 100644 index 00000000..c98ca143 --- /dev/null +++ b/internal/myks/render_global_ytt.go @@ -0,0 +1,53 @@ +package myks + +import ( + "github.com/rs/zerolog/log" + "path/filepath" +) + +type GlobalYtt struct { + ident string + app *Application + additive bool +} + +func (g *GlobalYtt) Ident() string { + return g.ident +} + +func (g *GlobalYtt) IsAdditive() bool { + return g.additive +} + +func (g *GlobalYtt) Render(previousStepFile string) (string, error) { + + var yttFiles []string + + yttFiles = append(yttFiles, g.app.yttDataFiles...) + + if previousStepFile != "" { + yttFiles = append(yttFiles, previousStepFile) + } + + yttFiles = append(yttFiles, g.app.e.collectBySubpath(filepath.Join("_env", g.app.e.g.YttPkgStepDirName))...) + + if len(yttFiles) == 0 { + log.Debug().Str("app", g.app.Name).Msg("No ytt files found") + return "", nil + } + + log.Debug().Str("step", "global-ytt").Strs("files", yttFiles).Str("app", g.app.Name).Msg("Collected ytt files") + + yttOutput, err := g.app.e.g.ytt(yttFiles) + if err != nil { + log.Warn().Err(err).Str("app", g.app.Name).Msg("Unable to render ytt files") + return "", err + } + + if yttOutput.Stdout == "" { + log.Warn().Str("app", g.app.Name).Msg("Empty ytt output") + return "", nil + } + + return yttOutput.Stdout, nil +} diff --git a/internal/myks/render_helm.go b/internal/myks/render_helm.go new file mode 100644 index 00000000..27ea6e12 --- /dev/null +++ b/internal/myks/render_helm.go @@ -0,0 +1,117 @@ +package myks + +import ( + "path/filepath" + "strings" + + "github.com/rs/zerolog/log" + yaml "gopkg.in/yaml.v3" +) + +type Helm struct { + ident string + app *Application + additive bool +} + +func (h *Helm) IsAdditive() bool { + return h.additive +} + +func (h *Helm) Ident() string { + return h.ident +} + +func (h *Helm) Render(_ string) (string, error) { + chartDir, err := h.app.getVendoredDir(h.app.e.g.HelmChartsDirName) + if err != nil { + log.Err(err).Str("app", h.app.Name).Msg("Unable to get helm charts dir") + return "", err + } + chartDirs := getSubDirs(chartDir) + if len(chartDirs) == 0 { + log.Debug().Str("app", h.app.Name).Msg("No charts to process") + return "", nil + } + + helmConfig, err := h.getHelmConfig() + if err != nil { + log.Warn().Err(err).Str("app", h.app.Name).Msg("Unable to get helm config") + return "", err + } + + var commonHelmArgs []string + + // FIXME: move Namespace to a per-chart config + if helmConfig.Namespace == "" { + helmConfig.Namespace = h.app.e.g.NamespacePrefix + h.app.Name + } + commonHelmArgs = append(commonHelmArgs, "--namespace", helmConfig.Namespace) + + if helmConfig.KubeVersion != "" { + commonHelmArgs = append(commonHelmArgs, "--kube-version", helmConfig.KubeVersion) + } + + // FIXME: move IncludeCRDs to a per-chart config + if helmConfig.IncludeCRDs { + commonHelmArgs = append(commonHelmArgs, "--include-crds") + } + + var outputs []string + + for _, chartDir := range chartDirs { + chartName := filepath.Base(chartDir) + var helmValuesFile string + if helmValuesFile, err = h.app.prepareValuesFile("helm", chartName); err != nil { + log.Warn().Err(err).Str("app", h.app.Name).Msg("Unable to prepare helm values") + return "", err + } + + // FIXME: replace h.app.Name with a name of the chart being processed + helmArgs := []string{ + "template", + "--skip-tests", + chartName, + chartDir, + } + + if helmValuesFile != "" { + helmArgs = append(helmArgs, "--values", helmValuesFile) + } + + res, err := runCmd("helm", nil, append(helmArgs, commonHelmArgs...)) + if err != nil { + log.Warn().Err(err).Str("stdout", res.Stdout).Str("stderr", res.Stderr).Msg("Unable to run helm") + return "", err + } + + if res.Stdout == "" { + log.Warn().Str("app", h.app.Name).Str("chart", chartName).Msg("No helm output") + continue + } + + outputs = append(outputs, res.Stdout) + + } + + return strings.Join(outputs, "---\n"), nil +} + +func (h *Helm) getHelmConfig() (HelmConfig, error) { + dataValuesYaml, err := h.app.e.g.ytt(h.app.yttDataFiles, "--data-values-inspect") + if err != nil { + log.Warn().Err(err).Str("app", h.app.Name).Msg("Unable to inspect data values") + return HelmConfig{}, err + } + + var helmConfig struct { + Helm HelmConfig + } + err = yaml.Unmarshal([]byte(dataValuesYaml.Stdout), &helmConfig) + if err != nil { + log.Warn().Err(err).Str("app", h.app.Name).Msg("Unable to unmarshal data values") + return HelmConfig{}, err + } + + return helmConfig.Helm, nil +} diff --git a/internal/myks/render_test.go b/internal/myks/render_test.go new file mode 100644 index 00000000..26158e37 --- /dev/null +++ b/internal/myks/render_test.go @@ -0,0 +1,116 @@ +package myks + +import ( + "os" + "testing" +) + +var testApp = &Application{ + Name: "", + Prototype: "", + e: &Environment{ + Id: "test-env", + g: &Globe{ + TempDirName: "/tmp", + }, + Dir: "/tmp", + }, + yttDataFiles: nil, + cached: false, + yttPkgDirs: nil, +} + +type TestTemplateTool struct { + ident string + app *Application + additive bool + renderedYaml string +} + +func (h *TestTemplateTool) IsAdditive() bool { + return h.additive +} + +func (h *TestTemplateTool) Ident() string { + return h.ident +} + +func (h *TestTemplateTool) Render(_ string) (string, error) { + return h.renderedYaml, nil +} + +func TestApplication_Render(t *testing.T) { + tests := []struct { + name string + args []YamlTemplatingTool + want string + wantYaml string + wantErr bool + }{ + { + name: "empty", + args: []YamlTemplatingTool{}, + want: "", + wantErr: false, + }, + { + name: "Additive", + args: []YamlTemplatingTool{ + &TestTemplateTool{ + ident: "test-template", + app: testApp, + additive: false, + renderedYaml: "step: One", + }, + &TestTemplateTool{ + ident: "test-template-2", + app: testApp, + additive: true, + renderedYaml: "step: Two", + }, + }, + want: "/tmp/_apps/tmp/steps/01-test-template-2.yaml", + wantYaml: "step: One\n---\nstep: Two", + wantErr: false, + }, + { + name: "Non-Additive", + args: []YamlTemplatingTool{ + &TestTemplateTool{ + ident: "test-template", + app: testApp, + additive: false, + renderedYaml: "step: One", + }, + &TestTemplateTool{ + ident: "test-template-2", + app: testApp, + additive: false, + renderedYaml: "step: Two", + }, + }, + want: "/tmp/_apps/tmp/steps/01-test-template-2.yaml", + wantYaml: "step: Two", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := testApp.Render(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("Render() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Render() got = %v, want %v", got, tt.want) + } + // check actual step file content + file, err := os.ReadFile(tt.want) + if err == nil { + if string(file) != tt.wantYaml { + t.Errorf("Render() got = %v, want %v", string(file), tt.wantYaml) + } + } + }) + } +} diff --git a/internal/myks/render_ytt.go b/internal/myks/render_ytt.go new file mode 100644 index 00000000..18f3ac65 --- /dev/null +++ b/internal/myks/render_ytt.go @@ -0,0 +1,63 @@ +package myks + +import ( + "github.com/rs/zerolog/log" + "os" + "path/filepath" +) + +type Ytt struct { + ident string + app *Application + additive bool +} + +func (y *Ytt) IsAdditive() bool { + return y.additive +} + +func (y *Ytt) Ident() string { + return y.ident +} + +func (y *Ytt) Render(previousStepFile string) (string, error) { + + var yttFiles []string + yttFiles = append(yttFiles, y.app.yttDataFiles...) + + if previousStepFile != "" { + yttFiles = append(yttFiles, previousStepFile) + } + + vendorYttDir := y.app.expandPath(filepath.Join(y.app.e.g.VendorDirName, y.app.e.g.YttStepDirName)) + if _, err := os.Stat(vendorYttDir); err == nil { + yttFiles = append(yttFiles, vendorYttDir) + } + + prototypeYttDir := filepath.Join(y.app.Prototype, y.app.e.g.YttStepDirName) + if _, err := os.Stat(prototypeYttDir); err == nil { + yttFiles = append(yttFiles, prototypeYttDir) + } + + yttFiles = append(yttFiles, y.app.e.collectBySubpath(filepath.Join("_apps", y.app.Name, y.app.e.g.YttStepDirName))...) + + if len(yttFiles) == 0 { + log.Debug().Str("app", y.app.Name).Msg("No yaml files found") + return "", nil + } + + log.Debug().Strs("files", yttFiles).Str("app", y.app.Name).Msg("Collected ytt files") + + yamlOutput, err := y.app.e.g.ytt(yttFiles) + if err != nil { + log.Warn().Err(err).Str("app", y.app.Name).Msg("Unable to render ytt files") + return "", err + } + + if yamlOutput.Stdout == "" { + log.Warn().Str("app", y.app.Name).Msg("Empty ytt output") + return "", nil + } + + return yamlOutput.Stdout, nil +} diff --git a/internal/myks/render_ytt_pkg.go b/internal/myks/render_ytt_pkg.go new file mode 100644 index 00000000..369e4265 --- /dev/null +++ b/internal/myks/render_ytt_pkg.go @@ -0,0 +1,73 @@ +package myks + +import ( + "github.com/rs/zerolog/log" + "path/filepath" + "strings" +) + +type YttPkg struct { + ident string + app *Application + additive bool +} + +func (y *YttPkg) IsAdditive() bool { + return y.additive +} + +func (y *YttPkg) Ident() string { + return y.ident +} + +func (y *YttPkg) Render(_ string) (string, error) { + yttPkgRootDir, err := y.app.getVendoredDir(y.app.e.g.YttPkgStepDirName) + if err != nil { + log.Err(err).Str("app", y.app.Name).Msg("Unable to get ytt package dir") + return "", err + } + yttPkgSubDirs := getSubDirs(yttPkgRootDir) + if len(yttPkgSubDirs) == 0 { + log.Debug().Str("app", y.app.Name).Msg("No packages to process") + return "", nil + } + + var outputs []string + + for _, pkgDir := range yttPkgSubDirs { + pkgName := filepath.Base(pkgDir) + var pkgValuesFile string + if pkgValuesFile, err = y.app.prepareValuesFile("ytt-pkg", pkgName); err != nil { + log.Warn().Err(err).Str("app", y.app.Name).Msg("Unable to prepare vendir packages value files") + return "", err + } + + var yttFiles []string + for _, yttFile := range y.app.yttPkgDirs { + yttFiles = append(yttFiles, filepath.Join(pkgDir, yttFile)) + } + if len(yttFiles) == 0 { + yttFiles = append(yttFiles, pkgDir) + } + + var yttArgs []string + if pkgValuesFile != "" { + yttArgs = append(yttArgs, "--data-values-file="+pkgValuesFile) + } + + res, err := runYttWithFilesAndStdin(yttFiles, nil, yttArgs...) + if err != nil { + log.Error().Err(err).Str("app", y.app.Name).Str("stdout", res.Stdout).Str("stderr", res.Stderr).Msg("Unable to run ytt") + return "", err + } + + if res.Stdout == "" { + log.Warn().Str("app", y.app.Name).Str("pkgName", pkgName).Msg("No ytt package output") + continue + } + + outputs = append(outputs, res.Stdout) + } + + return strings.Join(outputs, "---\n"), nil +} diff --git a/internal/myks/sync.go b/internal/myks/sync.go index 63825e7c..11167872 100644 --- a/internal/myks/sync.go +++ b/internal/myks/sync.go @@ -9,7 +9,7 @@ import ( "strings" ) -const secretEnvPrefix = "VENDIR_SECRET_" +const envPrefix = "VENDIR_SECRET_" type Directory struct { Path string @@ -17,6 +17,72 @@ type Directory struct { Secret string `yaml:"-"` } +func (a *Application) Sync() error { + if err := a.prepareSync(); err != nil { + if err == ErrNoVendirConfig { + return nil + } + return err + } + + if err := a.doSync(); err != nil { + return err + } + + return nil +} + +func (a *Application) prepareSync() error { + // Collect ytt arguments following the following steps: + // 1. If exists, use the `apps//vendir` directory. + // 2. If exists, for every level of environments use `/_apps//vendir` directory. + + var yttFiles []string + + protoVendirDir := filepath.Join(a.Prototype, "vendir") + if _, err := os.Stat(protoVendirDir); err == nil { + yttFiles = append(yttFiles, protoVendirDir) + log.Debug().Str("dir", protoVendirDir).Msg("Using prototype vendir directory") + } + + appVendirDirs := a.e.collectBySubpath(filepath.Join("_apps", a.Name, "vendir")) + yttFiles = append(yttFiles, appVendirDirs...) + + if len(yttFiles) == 0 { + err := ErrNoVendirConfig + log.Warn().Err(err).Str("app", a.Name).Msg("") + return err + } + + vendirConfig, err := a.e.g.ytt(yttFiles) + if err != nil { + log.Warn().Err(err).Str("app", a.Name).Msg("Unable to render vendir config") + return err + } + + if vendirConfig.Stdout == "" { + err = errors.New("Empty vendir config") + log.Warn().Err(err).Msg("") + return err + } + + vendirConfigFilePath := a.expandServicePath(a.e.g.VendirConfigFileName) + // Create directory if it does not exist + err = os.MkdirAll(filepath.Dir(vendirConfigFilePath), 0o750) + if err != nil { + log.Warn().Err(err).Msg("Unable to create directory for vendir config file") + return err + } + err = os.WriteFile(vendirConfigFilePath, []byte(vendirConfig.Stdout), 0o600) + if err != nil { + log.Warn().Err(err).Msg("Unable to write vendir config file") + return err + } + log.Debug().Str("app", a.Name).Str("file", vendirConfigFilePath).Msg("Wrote vendir config file") + + return nil +} + func (a *Application) doSync() error { // Paths are relative to the vendor directory (BUG: this will brake with multi-level vendor directory, e.g. `vendor/shmendor`) vendirConfigFileRelativePath := filepath.Join("..", a.e.g.ServiceDirName, a.e.g.VendirConfigFileName) @@ -70,7 +136,7 @@ func (a *Application) doSync() error { "--file=" + vendirConfigFileRelativePath, "--lock-file=" + vendirLockFileRelativePath, } - args, secretFilePath, err := handleVendirSecret(dir, a.expandTempPath(""), filepath.Join("..", a.e.g.ServiceDirName, a.e.g.TempDirName), args) + args, secretFilePath, err := handleVendirSecret(a.Name, dir, a.expandTempPath(""), filepath.Join("..", a.e.g.ServiceDirName, a.e.g.TempDirName), args) if err != nil { log.Error().Err(err).Str("app", a.Name).Msg("Unable to create secret for: " + dir.Path) return err @@ -96,7 +162,7 @@ func (a *Application) doSync() error { } for _, dir := range vendirDirs { var secretFilePath string - args, secretFilePath, err = handleVendirSecret(dir, a.expandTempPath(""), filepath.Join("..", a.e.g.ServiceDirName, a.e.g.TempDirName), args) + args, secretFilePath, err = handleVendirSecret(a.Name, dir, a.expandTempPath(""), filepath.Join("..", a.e.g.ServiceDirName, a.e.g.TempDirName), args) if err != nil { log.Error().Err(err).Str("app", a.Name).Msg("Unable to create secret for: " + dir.Path) return err @@ -150,7 +216,7 @@ func writeSyncFile(syncFilePath string, directories []Directory) error { return nil } -func handleVendirSecret(dir Directory, tempPath string, tempRelativePath string, vendirArgs []string) ([]string, string, error) { +func handleVendirSecret(app string, dir Directory, tempPath string, tempRelativePath string, vendirArgs []string) ([]string, string, error) { if dir.Secret != "" { username, password, err := getEnvCreds(dir.Secret) if err != nil { @@ -310,8 +376,8 @@ func checkLockFileMatch(vendirDirs []Directory, lockFileDirs []Directory) bool { } func getEnvCreds(secretName string) (string, string, error) { - username := os.Getenv(secretEnvPrefix + strings.ToUpper(secretName) + "_USERNAME") - password := os.Getenv(secretEnvPrefix + strings.ToUpper(secretName) + "_PASSWORD") + username := os.Getenv(envPrefix + strings.ToUpper(secretName) + "_USERNAME") + password := os.Getenv(envPrefix + strings.ToUpper(secretName) + "_PASSWORD") if username == "" || password == "" { return "", "", errors.New("no credentials found in environment for secret: " + secretName) } diff --git a/internal/myks/sync_test.go b/internal/myks/sync_test.go index b30915bb..1f45b25f 100644 --- a/internal/myks/sync_test.go +++ b/internal/myks/sync_test.go @@ -249,8 +249,8 @@ func Test_getEnvCreds(t *testing.T) { want1 string wantErr bool }{ - {"happy path", args{"loki-secret", secretEnvPrefix + "LOKI-SECRET_USERNAME", "username", secretEnvPrefix + "LOKI-SECRET_PASSWORD", "password"}, "username", "password", false}, - {"sad path", args{"loki-secret", secretEnvPrefix + "LOKI-SECRET_USERNAME", "", secretEnvPrefix + "LOKI-SECRET_PASSWORD", ""}, "", "", true}, + {"happy path", args{"loki-secret", envPrefix + "LOKI-SECRET_USERNAME", "username", envPrefix + "LOKI-SECRET_PASSWORD", "password"}, "username", "password", false}, + {"sad path", args{"loki-secret", envPrefix + "LOKI-SECRET_USERNAME", "", envPrefix + "LOKI-SECRET_PASSWORD", ""}, "", "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -340,7 +340,7 @@ func Test_handleVendirSecret(t *testing.T) { t.Run(tt.name, func(t *testing.T) { setEnvSafely(tt.args.envUsernameKey, tt.args.envUsernameValue, t) setEnvSafely(tt.args.envPasswordKey, tt.args.envPasswordValue, t) - got, secretPath, err := handleVendirSecret(tt.args.dir, tt.args.tempPath, tt.args.tempRelativePath, tt.args.vendirArgs) + got, secretPath, err := handleVendirSecret("", tt.args.dir, tt.args.tempPath, tt.args.tempRelativePath, tt.args.vendirArgs) if (err != nil) != tt.wantErr { t.Errorf("handleVendirSecret() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/internal/myks/util.go b/internal/myks/util.go index a6d7d46b..cf622334 100644 --- a/internal/myks/util.go +++ b/internal/myks/util.go @@ -127,6 +127,9 @@ func processItemsInParallel(collection interface{}, fn func(interface{}) error) } func copyFileSystemToPath(source fs.FS, sourcePath string, destinationPath string) error { + if err := os.MkdirAll(destinationPath, 0o755); err != nil { + return err + } err := fs.WalkDir(source, sourcePath, func(path string, d fs.DirEntry, err error) error { if err != nil { return err @@ -222,7 +225,7 @@ func hash(s string) string { return hex.EncodeToString(hash[:]) } -func renderDataYaml(dataFiles []string) ([]byte, error) { +func renderDataYaml(app string, dataFiles []string) ([]byte, error) { if len(dataFiles) == 0 { return nil, errors.New("No data files found") } @@ -276,3 +279,29 @@ func appendIfNotExists(slice []string, element string) ([]string, bool) { return append(slice, element), true } + +func getSubDirs(rootDir string) []string { + if rootDir == "" { + return []string{} + } + var resourceDirs []string + err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() && path != rootDir { + resourceDirs = append(resourceDirs, path) + return filepath.SkipDir + } + + return nil + }) + + if err != nil { + log.Warn().Err(err).Msg("Unable to walk vendor package directory") + return []string{} + } + + return resourceDirs +} diff --git a/internal/myks/util_test.go b/internal/myks/util_test.go index 6f798f12..7ee35161 100644 --- a/internal/myks/util_test.go +++ b/internal/myks/util_test.go @@ -108,7 +108,7 @@ func Test_renderDataYaml(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := renderDataYaml(tt.args.dataFiles) + got, err := renderDataYaml("", tt.args.dataFiles) if (err != nil) != tt.wantErr { t.Errorf("renderDataYaml() error = %v, wantErr %v", err, tt.wantErr) return @@ -215,3 +215,24 @@ func Test_reductSecrets(t *testing.T) { }) } } + +func Test_getSubDirs(t *testing.T) { + type args struct { + resourceDir string + } + tests := []struct { + name string + args args + want []string + }{ + {"happy path", args{"../../testData/vendor/charts"}, []string{"../../testData/vendor/charts/test-chart"}}, + {"empty", args{""}, []string{}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := getSubDirs(tt.args.resourceDir); !reflect.DeepEqual(got, tt.want) { + t.Errorf("getSubDirs() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/testData/vendor/charts/test-chart/sub-chart/.gitkeep b/testData/vendor/charts/test-chart/sub-chart/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/testData/vendor/ytt/test-resource/.hidden-dir/.gitkeep b/testData/vendor/ytt/test-resource/.hidden-dir/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/testData/vendor/ytt/test-resource/config/sub-dir/.gitkeep b/testData/vendor/ytt/test-resource/config/sub-dir/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/testData/vendor/ytt/test-resource/file b/testData/vendor/ytt/test-resource/file new file mode 100644 index 00000000..e69de29b diff --git a/testData/vendor/ytt/test-resource/manifests/.gitkeep b/testData/vendor/ytt/test-resource/manifests/.gitkeep new file mode 100644 index 00000000..e69de29b