diff --git a/go.mod b/go.mod index 39ec82719..93b4afaba 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 github.com/emicklei/go-restful v2.9.6+incompatible // indirect github.com/ghodss/yaml v1.0.0 // indirect - github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect + github.com/go-bindata/go-bindata v3.1.2+incompatible github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect github.com/google/btree v1.0.0 // indirect @@ -29,6 +29,7 @@ require ( github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/magiconair/properties v1.8.1 github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481 // indirect + github.com/manifoldco/promptui v0.6.0 github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect diff --git a/go.sum b/go.sum index 1bf6b4dac..0e63f3140 100644 --- a/go.sum +++ b/go.sum @@ -40,10 +40,14 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 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/alecthomas/gometalinter v3.0.0+incompatible h1:e9Zfvfytsw/e6Kd/PYd75wggK+/kX5Xn8IYDUKyc5fU= +github.com/alecthomas/gometalinter v3.0.0+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alessio/shellescape v0.0.0-20190409004728-b115ca0f9053 h1:H/GMMKYPkEIC3DF/JWQz8Pdd+Feifov2EIgGfNpeogI= github.com/alessio/shellescape v0.0.0-20190409004728-b115ca0f9053/go.mod h1:xW8sBma2LE3QxFSzCnH9qe6gAE2yO9GvQaWwX89HxbE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -58,6 +62,13 @@ github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17 github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/containerd v1.2.9 h1:6tyNjBmAMG47QuFPIT9LgiiexoVxC6qpTGR+eD0R0Z8= github.com/containerd/containerd v1.2.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -216,6 +227,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -229,6 +241,8 @@ github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1a github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/gophercloud v0.2.0 h1:lD2Bce2xBAMNNcFZ0dObTpXkGLlVIb33RPVUNVpw6ic= github.com/gophercloud/gophercloud v0.2.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY= +github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= @@ -270,6 +284,8 @@ github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62F github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= @@ -287,6 +303,8 @@ 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/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= @@ -297,10 +315,14 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481 h1:IaSjLMT6WvkoZZjspGxy3rdaTEmWLoRm49WbtVUi9sA= github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/manifoldco/promptui v0.6.0 h1:GuXmIdl5lhlamnWf3NbsKWYlaWyHABeStbD1LLsQMuA= +github.com/manifoldco/promptui v0.6.0/go.mod h1:o9/C5VV8IPXxjxpl9au84MtQGIi5dwn7eldAgEdePPs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= @@ -333,6 +355,8 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d h1:7PxY7LVfSZm7P github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nicksnyder/go-i18n v1.10.1 h1:isfg77E/aCD7+0lD/D00ebR2MV5vgeQ276WYyDaCRQc= +github.com/nicksnyder/go-i18n v1.10.1/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -416,6 +440,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 h1:vY5WqiEon0ZSTGM3ayVVi+twaHKHDFUVloaQ/wug9/c= +github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 h1:MPPkRncZLN9Kh4MEFmbnK4h3BD7AUmskWv2+EeZJCCs= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -497,6 +523,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h 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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -526,6 +553,7 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -564,6 +592,8 @@ google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20171010053543-63abe20a23e2 h1:5zOHKFi4LqGWG+3d+isqpbPrN/2yhDJnlO+BhRiuR6U= +gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20171010053543-63abe20a23e2/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA= 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= diff --git a/pkg/kudoctl/cmd/generate/operator.go b/pkg/kudoctl/cmd/generate/operator.go new file mode 100644 index 000000000..cbd3bd9f9 --- /dev/null +++ b/pkg/kudoctl/cmd/generate/operator.go @@ -0,0 +1,101 @@ +package generate + +import ( + "errors" + "fmt" + "path" + + "github.com/spf13/afero" + "sigs.k8s.io/yaml" + + "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" + "github.com/kudobuilder/kudo/pkg/kudoctl/packages" + "github.com/kudobuilder/kudo/pkg/kudoctl/packages/reader" +) + +// CanGenerateOperator checks to see if operator generation makes sense (we don't generate over an operator or existing folder) +// fails if folder exits (non-destructive) +// if "operator.yaml" exists in current dir, we assume it's a mistake an error +func CanGenerateOperator(fs afero.Fs, dir string, overwrite bool) error { + exists, err := afero.Exists(fs, dir) + if err != nil { + return err + } + if exists && !overwrite { + return fmt.Errorf("folder %q already exists", dir) + } + + exists, err = afero.Exists(fs, reader.OperatorFileName) + if err != nil { + return err + } + if exists { + return errors.New("operator.yaml exists in the current directory. creating an operator in an operator is not supported") + } + return nil +} + +// Operator generates an initial operator folder with a operator.yaml +func Operator(fs afero.Fs, dir string, op packages.OperatorFile, overwrite bool) error { + err := CanGenerateOperator(fs, dir, overwrite) + if err != nil { + return err + } + + exists, err := afero.DirExists(fs, dir) + if err != nil { + return err + } + + if !exists { + err = fs.Mkdir(dir, 0755) + } + if err != nil { + return err + } + + // required empty settings + op.Tasks = []v1beta1.Task{} + op.Plans = make(map[string]v1beta1.Plan) + + err = writeOperator(fs, dir, op) + if err != nil { + return err + } + + pfname := path.Join(dir, reader.ParamsFileName) + exists, err = afero.Exists(fs, pfname) + if err != nil { + return err + } + if exists { + return nil + } + + // if params doesn't exist create it + p := packages.ParamsFile{ + APIVersion: reader.APIVersion, + Parameters: []v1beta1.Parameter{}, + } + return writeParameters(fs, dir, p) +} + +func writeParameters(fs afero.Fs, dir string, params packages.ParamsFile) error { + p, err := yaml.Marshal(params) + if err != nil { + return err + } + + fname := path.Join(dir, reader.ParamsFileName) + return afero.WriteFile(fs, fname, p, 0755) +} + +func writeOperator(fs afero.Fs, dir string, op packages.OperatorFile) error { + o, err := yaml.Marshal(op) + if err != nil { + return err + } + + fname := path.Join(dir, reader.OperatorFileName) + return afero.WriteFile(fs, fname, o, 0755) +} diff --git a/pkg/kudoctl/cmd/generate/operator_test.go b/pkg/kudoctl/cmd/generate/operator_test.go new file mode 100644 index 000000000..5981d338b --- /dev/null +++ b/pkg/kudoctl/cmd/generate/operator_test.go @@ -0,0 +1,84 @@ +package generate + +import ( + "path" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + + "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" + "github.com/kudobuilder/kudo/pkg/kudoctl/packages" + "github.com/kudobuilder/kudo/pkg/kudoctl/packages/reader" +) + +func TestOperatorGenSafe(t *testing.T) { + + // empty fs should be fine + fs := afero.NewMemMapFs() + err := CanGenerateOperator(fs, "operator", false) + assert.Nil(t, err) + + // folder that doesn't exist should be fine + err = CanGenerateOperator(fs, "operator", false) + assert.Nil(t, err) + + _ = fs.Mkdir("operator", 0755) + // folder that exist should fail + err = CanGenerateOperator(fs, "operator", false) + assert.NotNil(t, err) + + // folder that exist should not fail if overwrite + err = CanGenerateOperator(fs, "operator", true) + assert.Nil(t, err) +} + +var ( + op1 = packages.OperatorFile{ + Name: "foo", + APIVersion: reader.APIVersion, + Version: "0.1.0", + } + opFilename = path.Join("operator", "operator.yaml") + paramFilename = path.Join("operator", "params.yaml") +) + +func TestOperator_Write(t *testing.T) { + + fs := afero.NewMemMapFs() + + err := Operator(fs, "operator", op1, false) + // no error on create + assert.Nil(t, err) + + // results in operator file + exists, _ := afero.Exists(fs, opFilename) + assert.True(t, exists) + // results in params file + exists, _ = afero.Exists(fs, paramFilename) + assert.True(t, exists) + + // test fail on existing + err = Operator(fs, "operator", op1, false) + assert.Errorf(t, err, "folder 'operator' already exists") + + // test overwriting with no error + err = Operator(fs, "operator", op1, true) + // no error on overwrite + assert.Nil(t, err) + + // updating params file and testing params are not overwritten + pf := packages.ParamsFile{ + APIVersion: "FOO", + Parameters: []v1beta1.Parameter{}, + } + // replace param file with a marker "FOO" to test that we do NOT overwrite it + err = writeParameters(fs, "operator", pf) + assert.Nil(t, err) + // test overwriting with no error + err = Operator(fs, "operator", op1, true) + // no error on overwrite + assert.Nil(t, err) + parmfile, _ := afero.ReadFile(fs, paramFilename) + assert.Contains(t, string(parmfile), "FOO") +} diff --git a/pkg/kudoctl/cmd/package.go b/pkg/kudoctl/cmd/package.go index 5a2fd4209..f84699ca3 100644 --- a/pkg/kudoctl/cmd/package.go +++ b/pkg/kudoctl/cmd/package.go @@ -29,6 +29,7 @@ func newPackageCmd(fs afero.Fs, out io.Writer) *cobra.Command { } cmd.AddCommand(newPackageCreateCmd(fs, out)) + cmd.AddCommand(newPackageNewCmd(fs, out)) cmd.AddCommand(newPackageParamsCmd(fs, out)) cmd.AddCommand(newPackageVerifyCmd(fs, out)) diff --git a/pkg/kudoctl/cmd/package_new.go b/pkg/kudoctl/cmd/package_new.go new file mode 100644 index 000000000..fe53a92d3 --- /dev/null +++ b/pkg/kudoctl/cmd/package_new.go @@ -0,0 +1,141 @@ +package cmd + +import ( + "errors" + "io" + + "github.com/Masterminds/semver" + "github.com/spf13/afero" + "github.com/spf13/cobra" + + "github.com/kudobuilder/kudo/pkg/kudoctl/cmd/generate" + "github.com/kudobuilder/kudo/pkg/kudoctl/cmd/prompt" + "github.com/kudobuilder/kudo/pkg/kudoctl/packages" + "github.com/kudobuilder/kudo/pkg/kudoctl/packages/reader" + "github.com/kudobuilder/kudo/pkg/version" +) + +const ( + pkgNewDesc = `Create a new KUDO operator on the local filesystem` + + pkgNewExample = ` # Create a new KUDO operator name foo + kubectl kudo package new foo +` +) + +type packageNewCmd struct { + name string + out io.Writer + fs afero.Fs + interactive bool + overwrite bool +} + +// newPackageNewCmd creates an operator package on the file system +func newPackageNewCmd(fs afero.Fs, out io.Writer) *cobra.Command { + + pkg := &packageNewCmd{out: out, fs: fs} + cmd := &cobra.Command{ + Use: "new ", + Short: "create new operator", + Long: pkgNewDesc, + Example: pkgNewExample, + RunE: func(cmd *cobra.Command, args []string) error { + if err := validateOperatorArg(args); err != nil { + return err + } + pkg.name = args[0] + if err := pkg.run(); err != nil { + return err + } + return nil + }, + } + + f := cmd.Flags() + f.BoolVarP(&pkg.interactive, "interactive", "i", false, "Interactively create operator") + f.BoolVarP(&pkg.overwrite, "overwrite", "w", false, "overwrite existing directory and operator.yaml file") + return cmd +} + +// run returns the errors associated with cmd env +func (pkg *packageNewCmd) run() error { + pathDefault := "operator" + ovDefault := "0.1.0" + kudoDefault := version.Get().GitVersion + apiVersionDefault := reader.APIVersion + + if !pkg.interactive { + op := packages.OperatorFile{ + Name: pkg.name, + APIVersion: apiVersionDefault, + Version: ovDefault, + KUDOVersion: kudoDefault, + } + + return generate.Operator(pkg.fs, pathDefault, op, pkg.overwrite) + } + + // interactive mode + nameValid := func(input string) error { + if len(input) < 3 { + return errors.New("Operator name must have more than 3 characters") + } + return nil + } + + name, err := prompt.WithValidator("Operator Name", pkg.name, nameValid) + if err != nil { + return err + } + + pathValid := func(input string) error { + if len(input) < 1 { + return errors.New("Operator directory must have more than 1 character") + } + return generate.CanGenerateOperator(pkg.fs, input, pkg.overwrite) + } + + path, err := prompt.WithValidator("Operator directory", pathDefault, pathValid) + if err != nil { + return err + } + + versionValid := func(input string) error { + if len(input) < 1 { + return errors.New("Operator version is required in semver format") + } + _, err := semver.NewVersion(input) + return err + } + opVersion, err := prompt.WithValidator("Operator Version", ovDefault, versionValid) + if err != nil { + return err + } + + appVersion, err := prompt.WithDefault("Application Version", "") + if err != nil { + return err + } + + kudoVersion, err := prompt.WithDefault("Required KUDO Version", kudoDefault) + if err != nil { + return err + } + + url, err := prompt.WithDefault("Project URL", "") + if err != nil { + return err + } + + op := packages.OperatorFile{ + Name: name, + APIVersion: apiVersionDefault, + Version: opVersion, + AppVersion: appVersion, + KUDOVersion: kudoVersion, + URL: url, + } + + return generate.Operator(pkg.fs, path, op, pkg.overwrite) +} diff --git a/pkg/kudoctl/cmd/package_new_test.go b/pkg/kudoctl/cmd/package_new_test.go new file mode 100644 index 000000000..300c8deba --- /dev/null +++ b/pkg/kudoctl/cmd/package_new_test.go @@ -0,0 +1,99 @@ +package cmd + +import ( + "bytes" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + + "github.com/kudobuilder/kudo/pkg/kudoctl/packages/reader" +) + +func TestPackageNew(t *testing.T) { + gfolder := "newop" + + fs := afero.NewMemMapFs() + out := &bytes.Buffer{} + cmd := newPackageNewCmd(fs, out) + err := cmd.RunE(cmd, []string{"newop"}) + if err != nil { + t.Fatal("unable to run package new command", err) + } + operatorFile := filepath.Join("operator", reader.OperatorFileName) + paramFile := filepath.Join("operator", reader.ParamsFileName) + + // comparison golden files + gOperatorFile := filepath.Join("testdata", gfolder, reader.OperatorFileName+".golden") + gParamFile := filepath.Join("testdata", gfolder, reader.ParamsFileName+".golden") + + operator, _ := afero.ReadFile(fs, operatorFile) + param, _ := afero.ReadFile(fs, paramFile) + + if *updateGolden { + t.Logf("updating golden file %s", gOperatorFile) + if err := ioutil.WriteFile(gOperatorFile, operator, 0644); err != nil { + t.Fatalf("failed to update golden file: %s", err) + } + t.Logf("updating golden file %s", gParamFile) + if err := ioutil.WriteFile(gParamFile, param, 0644); err != nil { + t.Fatalf("failed to update golden file: %s", err) + } + } + gOperator, err := ioutil.ReadFile(gOperatorFile) + if err != nil { + t.Fatalf("failed reading .golden: %s", err) + } + + gParam, err := ioutil.ReadFile(gParamFile) + if err != nil { + t.Fatalf("failed reading .golden: %s", err) + } + + assert.Equal(t, operator, gOperator, "for golden file: %s", gOperatorFile) + assert.Equal(t, param, gParam, "for golden file: %s", gParamFile) + +} + +func TestPackageNew_validation(t *testing.T) { + fs := afero.NewMemMapFs() + out := &bytes.Buffer{} + + var tests = []struct { + name string + args []string + errorMessage string + }{ + {name: "0 argument", args: []string{}, errorMessage: "expecting exactly one argument - directory of the operator or name of package"}, + {name: "2 arguments", args: []string{"1", "2"}, errorMessage: "expecting exactly one argument - directory of the operator or name of package"}, + } + + for _, test := range tests { + cmd := newPackageNewCmd(fs, out) + err := cmd.RunE(cmd, test.args) + assert.EqualError(t, err, test.errorMessage) + } + +} + +func TestPackageNew_Overwrite(t *testing.T) { + + fs := afero.NewMemMapFs() + out := &bytes.Buffer{} + cmd := newPackageNewCmd(fs, out) + err := cmd.RunE(cmd, []string{"newop"}) + if err != nil { + t.Fatal("unable to run package new command", err) + } + + // no overwrite + err = cmd.RunE(cmd, []string{"newop"}) + assert.EqualError(t, err, `folder "operator" already exists`) + + // overwrite with flag + _ = cmd.Flags().Set("overwrite", "true") + err = cmd.RunE(cmd, []string{"newop"}) + assert.Nil(t, err) +} diff --git a/pkg/kudoctl/cmd/prompt/prompt.go b/pkg/kudoctl/cmd/prompt/prompt.go new file mode 100644 index 000000000..165db2dd3 --- /dev/null +++ b/pkg/kudoctl/cmd/prompt/prompt.go @@ -0,0 +1,56 @@ +package prompt + +import ( + "strings" + + "github.com/manifoldco/promptui" +) + +func WithOptions(label string, options []string) (string, error) { + index := -1 + var err error + var result string + + for index < 0 { + prompt := promptui.SelectWithAdd{ + Label: label, + Items: options, + AddLabel: "Other", + } + + index, result, err = prompt.Run() + if index == -1 { + options = append(options, result) + } + } + + if err != nil { + return "", err + } + return strings.TrimSpace(result), nil + +} + +// input is output rune, in other words, no cursor +func cursor(input []rune) []rune { + return input +} + +func WithDefault(label string, defaultStr string) (string, error) { + return WithValidator(label, defaultStr, nil) +} + +func WithValidator(label string, defaultStr string, validate promptui.ValidateFunc) (string, error) { + prompt := promptui.Prompt{ + Label: label, + Default: defaultStr, + Validate: validate, + Pointer: cursor, + } + result, err := prompt.Run() + + if err != nil { + return "", err + } + return strings.TrimSpace(result), nil +} diff --git a/pkg/kudoctl/cmd/testdata/newop/operator.yaml.golden b/pkg/kudoctl/cmd/testdata/newop/operator.yaml.golden new file mode 100644 index 000000000..ba3cebe93 --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/newop/operator.yaml.golden @@ -0,0 +1,6 @@ +apiVersion: kudo.dev/v1beta1 +kudoVersion: dev +name: newop +plans: {} +tasks: [] +version: 0.1.0 diff --git a/pkg/kudoctl/cmd/testdata/newop/params.yaml.golden b/pkg/kudoctl/cmd/testdata/newop/params.yaml.golden new file mode 100644 index 000000000..8c72bb07b --- /dev/null +++ b/pkg/kudoctl/cmd/testdata/newop/params.yaml.golden @@ -0,0 +1,2 @@ +apiVersion: kudo.dev/v1beta1 +parameters: [] diff --git a/pkg/kudoctl/packages/reader/parser.go b/pkg/kudoctl/packages/reader/parser.go index 07a37bfd2..0a00436fa 100644 --- a/pkg/kudoctl/packages/reader/parser.go +++ b/pkg/kudoctl/packages/reader/parser.go @@ -14,8 +14,8 @@ import ( ) const ( - operatorFileName = "operator.yaml" - paramsFileName = "params.yaml" + OperatorFileName = "operator.yaml" + ParamsFileName = "params.yaml" templateBase = "templates" templateFileName = ".*\\.yaml" APIVersion = "kudo.dev/v1beta1" @@ -29,7 +29,7 @@ func newPackageFiles() packages.Files { func parsePackageFile(filePath string, fileBytes []byte, currentPackage *packages.Files) error { isOperatorFile := func(name string) bool { - return strings.HasSuffix(name, operatorFileName) + return strings.HasSuffix(name, OperatorFileName) } isTemplateFile := func(name string) bool { @@ -46,7 +46,7 @@ func parsePackageFile(filePath string, fileBytes []byte, currentPackage *package } isParametersFile := func(name string) bool { - return strings.HasSuffix(name, paramsFileName) + return strings.HasSuffix(name, ParamsFileName) } switch {