From cc5f6661a876b287c779431747c88d8dc1aa7920 Mon Sep 17 00:00:00 2001 From: varshaprasad96 Date: Fri, 25 Jun 2021 13:33:31 -0700 Subject: [PATCH 1/2] Update k8s dependencies, kb and helm plugin --- go.mod | 8 +- go.sum | 104 +++- .../internal/kubebuilder/cmdutil/cmdutil.go | 60 -- .../internal/kubebuilder/filesystem/errors.go | 173 ------ .../kubebuilder/filesystem/errors_test.go | 77 --- .../kubebuilder/filesystem/filesystem.go | 181 ------ .../kubebuilder/filesystem/filesystem_test.go | 150 ----- .../internal/kubebuilder/filesystem/mock.go | 217 ------- .../kubebuilder/filesystem/mock_test.go | 448 --------------- .../internal/kubebuilder/machinery/errors.go | 129 ++++- .../kubebuilder/machinery/errors_test.go | 42 +- .../internal/kubebuilder/machinery/file.go | 43 ++ .../filesystem.go} | 13 +- .../internal/kubebuilder/machinery/funcmap.go | 47 ++ .../kubebuilder/machinery/funcmap_test.go | 45 ++ .../kubebuilder/machinery/injector.go | 66 +++ .../kubebuilder/machinery/injector_test.go | 295 ++++++++++ .../kubebuilder/machinery/interfaces.go | 104 ++++ .../internal/kubebuilder/machinery/marker.go | 71 +++ .../kubebuilder/machinery/marker_test.go | 47 ++ .../internal/kubebuilder/machinery/mixins.go | 153 +++++ .../kubebuilder/machinery/mixins_test.go | 188 +++++++ .../kubebuilder/machinery/scaffold.go | 278 +++++---- .../kubebuilder/machinery/scaffold_test.go | 529 ++++++++---------- pkg/plugins/util/cleanup.go | 137 +++++ pkg/plugins/util/utils.go | 81 +++ pkg/plugins/v1/api.go | 211 ++++--- pkg/plugins/v1/chartutil/chart.go | 241 +++----- pkg/plugins/v1/chartutil/chart_test.go | 116 +--- pkg/plugins/v1/init.go | 152 +++-- pkg/plugins/v1/scaffolds/api.go | 93 ++- pkg/plugins/v1/scaffolds/init.go | 62 +- .../internal/templates/config/crd/crd.go | 29 +- .../templates/config/crd/kustomization.go | 47 +- .../config/kdefault/auth_proxy_patch.go | 74 --- .../config/kdefault/kustomization.go | 73 --- .../templates/config/manager/kustomization.go | 47 -- .../templates/config/manager/manager.go | 98 ---- .../config/prometheus/kustomization.go | 45 -- .../templates/config/prometheus/monitor.go | 59 -- .../templates/config/rbac/auth_proxy_role.go | 56 -- .../config/rbac/auth_proxy_role_binding.go | 55 -- .../config/rbac/auth_proxy_service.go | 57 -- .../config/rbac/client_cluster_role.go | 50 -- .../templates/config/rbac/crd_editor_rbac.go | 69 --- .../templates/config/rbac/crd_viewer_rbac.go | 65 --- .../templates/config/rbac/kustomization.go | 57 -- .../config/rbac/leader_election_role.go | 68 --- .../rbac/leader_election_role_binding.go | 55 -- .../templates/config/rbac/manager_role.go | 34 +- .../config/rbac/manager_role_binding.go | 55 -- .../{crd_sample.go => custom_resources.go} | 32 +- .../internal/templates/dockerfile.go | 12 +- .../scaffolds/internal/templates/gitignore.go | 8 +- .../scaffolds/internal/templates/makefile.go | 65 ++- .../scaffolds/internal/templates/watches.go | 31 +- 56 files changed, 2400 insertions(+), 3402 deletions(-) delete mode 100644 pkg/plugins/internal/kubebuilder/cmdutil/cmdutil.go delete mode 100644 pkg/plugins/internal/kubebuilder/filesystem/errors.go delete mode 100644 pkg/plugins/internal/kubebuilder/filesystem/errors_test.go delete mode 100644 pkg/plugins/internal/kubebuilder/filesystem/filesystem.go delete mode 100644 pkg/plugins/internal/kubebuilder/filesystem/filesystem_test.go delete mode 100644 pkg/plugins/internal/kubebuilder/filesystem/mock.go delete mode 100644 pkg/plugins/internal/kubebuilder/filesystem/mock_test.go create mode 100644 pkg/plugins/internal/kubebuilder/machinery/file.go rename pkg/plugins/internal/kubebuilder/{filesystem/filesystem_suite_test.go => machinery/filesystem.go} (76%) create mode 100644 pkg/plugins/internal/kubebuilder/machinery/funcmap.go create mode 100644 pkg/plugins/internal/kubebuilder/machinery/funcmap_test.go create mode 100644 pkg/plugins/internal/kubebuilder/machinery/injector.go create mode 100644 pkg/plugins/internal/kubebuilder/machinery/injector_test.go create mode 100644 pkg/plugins/internal/kubebuilder/machinery/interfaces.go create mode 100644 pkg/plugins/internal/kubebuilder/machinery/marker.go create mode 100644 pkg/plugins/internal/kubebuilder/machinery/marker_test.go create mode 100644 pkg/plugins/internal/kubebuilder/machinery/mixins.go create mode 100644 pkg/plugins/internal/kubebuilder/machinery/mixins_test.go create mode 100644 pkg/plugins/util/cleanup.go create mode 100644 pkg/plugins/util/utils.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/kdefault/kustomization.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/manager/kustomization.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/manager/manager.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/prometheus/kustomization.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/prometheus/monitor.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_service.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/client_cluster_role.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/crd_editor_rbac.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/crd_viewer_rbac.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/kustomization.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/leader_election_role.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go delete mode 100644 pkg/plugins/v1/scaffolds/internal/templates/config/rbac/manager_role_binding.go rename pkg/plugins/v1/scaffolds/internal/templates/config/samples/{crd_sample.go => custom_resources.go} (70%) diff --git a/go.mod b/go.mod index cd56b77d..1101b475 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/go-logr/logr v0.3.0 github.com/iancoleman/strcase v0.1.2 github.com/kr/text v0.1.0 - github.com/onsi/ginkgo v1.14.1 - github.com/onsi/gomega v1.10.2 + github.com/onsi/ginkgo v1.15.0 + github.com/onsi/gomega v1.10.5 github.com/operator-framework/operator-lib v0.3.0 github.com/prometheus/client_golang v1.7.1 github.com/sirupsen/logrus v1.7.0 @@ -17,7 +17,7 @@ require ( github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.6.1 - golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5 + golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e gomodules.xyz/jsonpatch/v2 v2.1.0 helm.sh/helm/v3 v3.5.0 k8s.io/api v0.20.4 @@ -27,7 +27,7 @@ require ( k8s.io/client-go v0.20.4 k8s.io/kubectl v0.20.4 sigs.k8s.io/controller-runtime v0.8.2 - sigs.k8s.io/kubebuilder/v3 v3.0.0-beta.0 + sigs.k8s.io/kubebuilder/v3 v3.1.0 sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index dd2754e6..fb322c1b 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -24,6 +25,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 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= @@ -52,6 +54,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= @@ -70,6 +74,7 @@ github.com/Microsoft/hcsshim v0.8.7 h1:ptnOoufxGSzauVTsdE+wMYnCWA301PdoN4xg5oRdZ github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -84,11 +89,13 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -131,6 +138,7 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= @@ -141,6 +149,11 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= +github.com/cloudflare/cfssl v1.5.0 h1:vFJDAvQgFSRbCn9zg8KpSrrEZrBAQ4KO5oNK7SXEyb0= +github.com/cloudflare/cfssl v1.5.0/go.mod h1:sPPkBS5L8l8sRc/IOO1jG51Xb34u+TYhL6P//JdODMQ= +github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4= +github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f h1:tSNMc+rJDfmYntojat8lljbt1mgKNpTxUZJsSzJ9Y1s= @@ -176,6 +189,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -211,6 +225,7 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustmop/soup v1.1.2-0.20190516214245-38228baa104e/go.mod h1:CgNC6SGbT+Xb8wGGvzilttZL1mc5sQ/5KkcxsZttMIk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= @@ -241,12 +256,15 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -295,12 +313,14 @@ github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nA github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw= +github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= @@ -310,6 +330,7 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -320,6 +341,8 @@ github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6 github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/gobuffalo/flect v0.2.2 h1:PAVD7sp0KOdfswjAw9BpLCU9hXo7wFSzgpQ+zNeks/A= github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= +github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= @@ -372,6 +395,8 @@ github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA// github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -462,9 +487,11 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= @@ -482,6 +509,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= +github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -493,12 +522,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -516,6 +547,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= 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= @@ -530,6 +563,7 @@ github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do= github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -562,8 +596,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -576,6 +614,7 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= @@ -589,19 +628,19 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4= +github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= +github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -628,6 +667,7 @@ github.com/operator-framework/operator-lib v0.3.0 h1:eGJv0k7dEHIT9vfArWPo6U4eVur github.com/operator-framework/operator-lib v0.3.0/go.mod h1:LTp5UQd8ivq4MXqm/W/XHulHQ0RRoZXsAj73sNMAQxc= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulmach/orb v0.1.3/go.mod h1:VFlX/8C+IQ1p6FTRRKzKoOPJnvEtA5G0Veuqwbu//Vk= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= @@ -682,6 +722,7 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/qri-io/starlib v0.4.2-0.20200213133954-ff2e8cd5ef8d/go.mod h1:7DPO4domFU579Ga6E61sB9VFNaniPVwJP5C4bBCu3wA= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -699,11 +740,14 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -724,7 +768,6 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= @@ -744,8 +787,10 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/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= @@ -761,7 +806,12 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/weppos/publicsuffix-go v0.13.0 h1:0Tu1uzLBd1jPn4k6OnMmOPZH/l/9bj9kUOMMkoRs6Gg= +github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -770,8 +820,11 @@ github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= @@ -780,6 +833,13 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1 github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= +github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= +github.com/zmap/zcrypto v0.0.0-20200513165325-16679db567ff/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4= +github.com/zmap/zcrypto v0.0.0-20200911161511-43ff0ea04f21 h1:PIpcdSOg3pMdFJUBg5yR9xxcj5rm/SGAyaWT/wK6Kco= +github.com/zmap/zcrypto v0.0.0-20200911161511-43ff0ea04f21/go.mod h1:TxpejqcVKQjQaVVmMGfzx5HnmFMdIU+vLtaCyPBfGI4= +github.com/zmap/zlint/v2 v2.2.1 h1:b2kI/ToXX16h2wjV2c6Da65eT6aTMtkLHKetXuM9EtI= +github.com/zmap/zlint/v2 v2.2.1/go.mod h1:ixPWsdq8qLxYRpNUTbcKig3R7WgmspsHGLhCCs6rFAM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -796,6 +856,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.starlark.net v0.0.0-20190528202925-30ae18b8564f/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -830,12 +892,14 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200124225646-8b5121be2f68/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -869,6 +933,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -903,8 +968,11 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -916,8 +984,9 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -948,6 +1017,7 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -967,8 +1037,9 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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= @@ -1030,11 +1101,11 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200403190813-44a64ad78b9b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5 h1:UaoXseXAWUJUcuJ2E2oczJdLxAJXL0lOmVaBl7kuk+I= golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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= @@ -1140,6 +1211,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1253,11 +1325,15 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyz sigs.k8s.io/controller-runtime v0.7.0/go.mod h1:pJ3YBrJiAqMAZKi6UVGuE98ZrroV1p+pIhoHsMm9wdU= sigs.k8s.io/controller-runtime v0.8.2 h1:SBWmI0b3uzMIUD/BIXWNegrCeZmPJ503pOtwxY0LPHM= sigs.k8s.io/controller-runtime v0.8.2/go.mod h1:U/l+DUopBc1ecfRZ5aviA9JDmGFQKvLf5YkZNx2e0sU= +sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= +sigs.k8s.io/controller-tools v0.4.1 h1:VkuV0MxlRPmRu5iTgBZU4UxUX2LiR99n3sdQGRxZF4w= sigs.k8s.io/controller-tools v0.4.1/go.mod h1:G9rHdZMVlBDocIxGkK3jHLWqcTMNvveypYJwrvYKjWU= -sigs.k8s.io/kubebuilder/v3 v3.0.0-beta.0 h1:TXXv53toWFNR1EGU8bFfE+XaSuzzbBA/nlDRz2FbimY= -sigs.k8s.io/kubebuilder/v3 v3.0.0-beta.0/go.mod h1:b1WkCy5t/3VSRBCffSfPV1WbH+f45ls69d4ic37sW6w= +sigs.k8s.io/kubebuilder/v3 v3.1.0 h1:LPgQQcKtVwqJ19gRboBYJHRG9a/wDeP3fXO7czTKVwE= +sigs.k8s.io/kubebuilder/v3 v3.1.0/go.mod h1:kWdZWaDD6/+IEU+fX9OH6yD8XjEHBvgfcd8WjjJ9qDo= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= +sigs.k8s.io/kustomize/kyaml v0.10.10 h1:caAxDDkaXZp+0kDsZVik4leFJV8LCy09PdVqpaoNeF4= +sigs.k8s.io/kustomize/kyaml v0.10.10/go.mod h1:K9yg1k/HB/6xNOf5VH3LhTo1DK9/5ykSZO5uIv+Y/1k= sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= diff --git a/pkg/plugins/internal/kubebuilder/cmdutil/cmdutil.go b/pkg/plugins/internal/kubebuilder/cmdutil/cmdutil.go deleted file mode 100644 index aa3e7f4a..00000000 --- a/pkg/plugins/internal/kubebuilder/cmdutil/cmdutil.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cmdutil - -// Scaffolder interface creates files to set up a controller manager -type Scaffolder interface { - // Scaffold performs the scaffolding - Scaffold() error -} - -// RunOptions represent the types used to implement the different commands -type RunOptions interface { - // - Step 1: verify that the command can be run (e.g., go version, project version, arguments, ...) - Validate() error - // - Step 2: create the Scaffolder instance - GetScaffolder() (Scaffolder, error) - // - Step 3: call the Scaffold method of the Scaffolder instance. Doesn't need any method - // - Step 4: finish the command execution - PostScaffold() error -} - -// Run executes a command -func Run(options RunOptions) error { - // Step 1: validate - if err := options.Validate(); err != nil { - return err - } - - // Step 2: get scaffolder - scaffolder, err := options.GetScaffolder() - if err != nil { - return err - } - // Step 3: scaffold - if scaffolder != nil { - if err := scaffolder.Scaffold(); err != nil { - return err - } - } - // Step 4: finish - if err := options.PostScaffold(); err != nil { - return err - } - - return nil -} diff --git a/pkg/plugins/internal/kubebuilder/filesystem/errors.go b/pkg/plugins/internal/kubebuilder/filesystem/errors.go deleted file mode 100644 index 7f605d32..00000000 --- a/pkg/plugins/internal/kubebuilder/filesystem/errors.go +++ /dev/null @@ -1,173 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package filesystem - -import ( - "errors" - "fmt" -) - -// This file contains the errors returned by the file system wrapper -// They are not exported as they should not be created outside of this package -// Exported functions are provided to check which kind of error was returned - -// fileExistsError is returned if it could not be checked if the file exists -type fileExistsError struct { - path string - err error -} - -// Error implements error interface -func (e fileExistsError) Error() string { - return fmt.Sprintf("failed to check if %s exists: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e fileExistsError) Unwrap() error { - return e.err -} - -// IsFileExistsError checks if the returned error is because the file could not be checked for existence -func IsFileExistsError(err error) bool { - return errors.As(err, &fileExistsError{}) -} - -// openFileError is returned if the file could not be opened -type openFileError struct { - path string - err error -} - -// Error implements error interface -func (e openFileError) Error() string { - return fmt.Sprintf("failed to open %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e openFileError) Unwrap() error { - return e.err -} - -// IsOpenFileError checks if the returned error is because the file could not be opened -func IsOpenFileError(err error) bool { - return errors.As(err, &openFileError{}) -} - -// createDirectoryError is returned if the directory could not be created -type createDirectoryError struct { - path string - err error -} - -// Error implements error interface -func (e createDirectoryError) Error() string { - return fmt.Sprintf("failed to create directory for %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e createDirectoryError) Unwrap() error { - return e.err -} - -// IsCreateDirectoryError checks if the returned error is because the directory could not be created -func IsCreateDirectoryError(err error) bool { - return errors.As(err, &createDirectoryError{}) -} - -// createFileError is returned if the file could not be created -type createFileError struct { - path string - err error -} - -// Error implements error interface -func (e createFileError) Error() string { - return fmt.Sprintf("failed to create %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e createFileError) Unwrap() error { - return e.err -} - -// IsCreateFileError checks if the returned error is because the file could not be created -func IsCreateFileError(err error) bool { - return errors.As(err, &createFileError{}) -} - -// readFileError is returned if the file could not be read -type readFileError struct { - path string - err error -} - -// Error implements error interface -func (e readFileError) Error() string { - return fmt.Sprintf("failed to read from %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e readFileError) Unwrap() error { - return e.err -} - -// IsReadFileError checks if the returned error is because the file could not be read -func IsReadFileError(err error) bool { - return errors.As(err, &readFileError{}) -} - -// writeFileError is returned if the file could not be written -type writeFileError struct { - path string - err error -} - -// Error implements error interface -func (e writeFileError) Error() string { - return fmt.Sprintf("failed to write to %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e writeFileError) Unwrap() error { - return e.err -} - -// IsWriteFileError checks if the returned error is because the file could not be written to -func IsWriteFileError(err error) bool { - return errors.As(err, &writeFileError{}) -} - -// closeFileError is returned if the file could not be created -type closeFileError struct { - path string - err error -} - -// Error implements error interface -func (e closeFileError) Error() string { - return fmt.Sprintf("failed to close %s: %v", e.path, e.err) -} - -// Unwrap implements Wrapper interface -func (e closeFileError) Unwrap() error { - return e.err -} - -// IsCloseFileError checks if the returned error is because the file could not be closed -func IsCloseFileError(err error) bool { - return errors.As(err, &closeFileError{}) -} diff --git a/pkg/plugins/internal/kubebuilder/filesystem/errors_test.go b/pkg/plugins/internal/kubebuilder/filesystem/errors_test.go deleted file mode 100644 index 090101fa..00000000 --- a/pkg/plugins/internal/kubebuilder/filesystem/errors_test.go +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package filesystem - -import ( - "errors" - "path/filepath" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" -) - -var _ = Describe("Errors", func() { - var ( - path = filepath.Join("path", "to", "file") - err = errors.New("test error") - fileExistsErr = fileExistsError{path, err} - openFileErr = openFileError{path, err} - createDirectoryErr = createDirectoryError{path, err} - createFileErr = createFileError{path, err} - readFileErr = readFileError{path, err} - writeFileErr = writeFileError{path, err} - closeFileErr = closeFileError{path, err} - ) - - DescribeTable("IsXxxxError should return true for themselves and false for the rest", - func(f func(error) bool, itself error, rest ...error) { - Expect(f(itself)).To(BeTrue()) - for _, err := range rest { - Expect(f(err)).To(BeFalse()) - } - }, - Entry("file exists", IsFileExistsError, fileExistsErr, - openFileErr, createDirectoryErr, createFileErr, readFileErr, writeFileErr, closeFileErr), - Entry("open file", IsOpenFileError, openFileErr, - fileExistsErr, createDirectoryErr, createFileErr, readFileErr, writeFileErr, closeFileErr), - Entry("create directory", IsCreateDirectoryError, createDirectoryErr, - fileExistsErr, openFileErr, createFileErr, readFileErr, writeFileErr, closeFileErr), - Entry("create file", IsCreateFileError, createFileErr, - fileExistsErr, openFileErr, createDirectoryErr, readFileErr, writeFileErr, closeFileErr), - Entry("read file", IsReadFileError, readFileErr, - fileExistsErr, openFileErr, createDirectoryErr, createFileErr, writeFileErr, closeFileErr), - Entry("write file", IsWriteFileError, writeFileErr, - fileExistsErr, openFileErr, createDirectoryErr, createFileErr, readFileErr, closeFileErr), - Entry("close file", IsCloseFileError, closeFileErr, - fileExistsErr, openFileErr, createDirectoryErr, createFileErr, readFileErr, writeFileErr), - ) - - DescribeTable("should contain the wrapped error and error message", - func(err error) { - Expect(err).To(MatchError(err)) - Expect(err.Error()).To(ContainSubstring(err.Error())) - }, - Entry("file exists", fileExistsErr), - Entry("open file", openFileErr), - Entry("create directory", createDirectoryErr), - Entry("create file", createFileErr), - Entry("read file", readFileErr), - Entry("write file", writeFileErr), - Entry("close file", closeFileErr), - ) -}) diff --git a/pkg/plugins/internal/kubebuilder/filesystem/filesystem.go b/pkg/plugins/internal/kubebuilder/filesystem/filesystem.go deleted file mode 100644 index e7e362c5..00000000 --- a/pkg/plugins/internal/kubebuilder/filesystem/filesystem.go +++ /dev/null @@ -1,181 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package filesystem - -import ( - "io" - "os" - "path/filepath" - - "github.com/spf13/afero" -) - -const ( - createOrUpdate = os.O_WRONLY | os.O_CREATE | os.O_TRUNC - - defaultDirectoryPermission os.FileMode = 0700 - defaultFilePermission os.FileMode = 0600 -) - -// FileSystem is an IO wrapper to create files -type FileSystem interface { - // Exists checks if the file exists - Exists(path string) (bool, error) - - // Open opens the file and returns a self-closing io.Reader. - Open(path string) (io.ReadCloser, error) - - // Create creates the directory and file and returns a self-closing - // io.Writer pointing to that file. If the file exists, it truncates it. - Create(path string) (io.Writer, error) -} - -// fileSystem implements FileSystem -type fileSystem struct { - fs afero.Fs - dirPerm os.FileMode - filePerm os.FileMode - fileMode int -} - -// New returns a new FileSystem -func New(options ...Options) FileSystem { - // Default values - fs := fileSystem{ - fs: afero.NewOsFs(), - dirPerm: defaultDirectoryPermission, - filePerm: defaultFilePermission, - fileMode: createOrUpdate, - } - - // Apply options - for _, option := range options { - option(&fs) - } - - return fs -} - -// Options configure FileSystem -type Options func(system *fileSystem) - -// DirectoryPermissions makes FileSystem.Create use the provided directory -// permissions -func DirectoryPermissions(dirPerm os.FileMode) Options { - return func(fs *fileSystem) { - fs.dirPerm = dirPerm - } -} - -// FilePermissions makes FileSystem.Create use the provided file permissions -func FilePermissions(filePerm os.FileMode) Options { - return func(fs *fileSystem) { - fs.filePerm = filePerm - } -} - -// Exists implements FileSystem.Exists -func (fs fileSystem) Exists(path string) (bool, error) { - exists, err := afero.Exists(fs.fs, path) - if err != nil { - return exists, fileExistsError{path, err} - } - - return exists, nil -} - -// Open implements FileSystem.Open -func (fs fileSystem) Open(path string) (io.ReadCloser, error) { - rc, err := fs.fs.Open(path) - if err != nil { - return nil, openFileError{path, err} - } - - return &readFile{path, rc}, nil -} - -// Create implements FileSystem.Create -func (fs fileSystem) Create(path string) (io.Writer, error) { - // Create the directory if needed - if err := fs.fs.MkdirAll(filepath.Dir(path), fs.dirPerm); err != nil { - return nil, createDirectoryError{path, err} - } - - // Create or truncate the file - wc, err := fs.fs.OpenFile(path, fs.fileMode, fs.filePerm) - if err != nil { - return nil, createFileError{path, err} - } - - return &writeFile{path, wc}, nil -} - -var _ io.ReadCloser = &readFile{} - -// readFile implements io.Reader -type readFile struct { - path string - io.ReadCloser -} - -// Read implements io.Reader.ReadCloser -func (f *readFile) Read(content []byte) (n int, err error) { - // Read the content - n, err = f.ReadCloser.Read(content) - // EOF is a special case error that we can't wrap - if err == io.EOF { - return - } - if err != nil { - return n, readFileError{f.path, err} - } - - return n, nil -} - -// Close implements io.Reader.ReadCloser -func (f *readFile) Close() error { - if err := f.ReadCloser.Close(); err != nil { - return closeFileError{f.path, err} - } - - return nil -} - -// writeFile implements io.Writer -type writeFile struct { - path string - io.WriteCloser -} - -// Write implements io.Writer.Write -func (f *writeFile) Write(content []byte) (n int, err error) { - // Close the file when we end writing - defer func() { - if closeErr := f.Close(); err == nil && closeErr != nil { - err = closeFileError{f.path, err} - } - }() - - // Write the content - n, err = f.WriteCloser.Write(content) - if err != nil { - return n, writeFileError{f.path, err} - } - - return n, nil -} diff --git a/pkg/plugins/internal/kubebuilder/filesystem/filesystem_test.go b/pkg/plugins/internal/kubebuilder/filesystem/filesystem_test.go deleted file mode 100644 index 6a3d216f..00000000 --- a/pkg/plugins/internal/kubebuilder/filesystem/filesystem_test.go +++ /dev/null @@ -1,150 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package filesystem - -import ( - "os" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("FileSystem", func() { - Describe("New", func() { - const ( - dirPerm os.FileMode = 0777 - filePerm os.FileMode = 0666 - ) - - var ( - fsi FileSystem - fs fileSystem - ok bool - ) - - Context("when using no options", func() { - BeforeEach(func() { - fsi = New() - fs, ok = fsi.(fileSystem) - }) - - It("should be a fileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(fs.fs).NotTo(BeNil()) - }) - - It("should use default directory permission", func() { - Expect(fs.dirPerm).To(Equal(defaultDirectoryPermission)) - }) - - It("should use default file permission", func() { - Expect(fs.filePerm).To(Equal(defaultFilePermission)) - }) - - It("should use default file mode", func() { - Expect(fs.fileMode).To(Equal(createOrUpdate)) - }) - }) - - Context("when using directory permission option", func() { - BeforeEach(func() { - fsi = New(DirectoryPermissions(dirPerm)) - fs, ok = fsi.(fileSystem) - }) - - It("should be a fileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(fs.fs).NotTo(BeNil()) - }) - - It("should use provided directory permission", func() { - Expect(fs.dirPerm).To(Equal(dirPerm)) - }) - - It("should use default file permission", func() { - Expect(fs.filePerm).To(Equal(defaultFilePermission)) - }) - - It("should use default file mode", func() { - Expect(fs.fileMode).To(Equal(createOrUpdate)) - }) - }) - - Context("when using file permission option", func() { - BeforeEach(func() { - fsi = New(FilePermissions(filePerm)) - fs, ok = fsi.(fileSystem) - }) - - It("should be a fileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(fs.fs).NotTo(BeNil()) - }) - - It("should use default directory permission", func() { - Expect(fs.dirPerm).To(Equal(defaultDirectoryPermission)) - }) - - It("should use provided file permission", func() { - Expect(fs.filePerm).To(Equal(filePerm)) - }) - - It("should use default file mode", func() { - Expect(fs.fileMode).To(Equal(createOrUpdate)) - }) - }) - - Context("when using both directory and file permission options", func() { - BeforeEach(func() { - fsi = New(DirectoryPermissions(dirPerm), FilePermissions(filePerm)) - fs, ok = fsi.(fileSystem) - }) - - It("should be a fileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(fs.fs).NotTo(BeNil()) - }) - - It("should use provided directory permission", func() { - Expect(fs.dirPerm).To(Equal(dirPerm)) - }) - - It("should use provided file permission", func() { - Expect(fs.filePerm).To(Equal(filePerm)) - }) - - It("should use default file mode", func() { - Expect(fs.fileMode).To(Equal(createOrUpdate)) - }) - }) - }) - - // NOTE: FileSystem.Exists, FileSystem.Open, FileSystem.Open().Read, FileSystem.Create and FileSystem.Create().Write - // are hard to test in unitary tests as they deal with actual files -}) diff --git a/pkg/plugins/internal/kubebuilder/filesystem/mock.go b/pkg/plugins/internal/kubebuilder/filesystem/mock.go deleted file mode 100644 index b7d213c1..00000000 --- a/pkg/plugins/internal/kubebuilder/filesystem/mock.go +++ /dev/null @@ -1,217 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package filesystem - -import ( - "bytes" - "io" -) - -// mockFileSystem implements FileSystem -type mockFileSystem struct { - path string - exists func(path string) bool - existsError error - openFileError error - createDirError error - createFileError error - input *bytes.Buffer - readFileError error - output *bytes.Buffer - writeFileError error - closeFileError error -} - -// NewMock returns a new FileSystem -func NewMock(options ...MockOptions) FileSystem { - // Default values - fs := mockFileSystem{ - exists: func(_ string) bool { return false }, - output: new(bytes.Buffer), - } - - // Apply options - for _, option := range options { - option(&fs) - } - - return fs -} - -// MockOptions configure FileSystem -type MockOptions func(system *mockFileSystem) - -// MockPath ensures that the file created with this scaffold is at path -func MockPath(path string) MockOptions { - return func(fs *mockFileSystem) { - fs.path = path - } -} - -// MockExists makes FileSystem.Exists use the provided function to check if the file exists -func MockExists(exists func(path string) bool) MockOptions { - return func(fs *mockFileSystem) { - fs.exists = exists - } -} - -// MockExistsError makes FileSystem.Exists return err -func MockExistsError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.existsError = err - } -} - -// MockOpenFileError makes FileSystem.Open return err -func MockOpenFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.openFileError = err - } -} - -// MockCreateDirError makes FileSystem.Create return err -func MockCreateDirError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.createDirError = err - } -} - -// MockCreateFileError makes FileSystem.Create return err -func MockCreateFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.createFileError = err - } -} - -// MockInput provides a buffer where the content will be read from -func MockInput(input *bytes.Buffer) MockOptions { - return func(fs *mockFileSystem) { - fs.input = input - } -} - -// MockReadFileError makes the Read method (of the io.Reader returned by FileSystem.Open) return err -func MockReadFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.readFileError = err - } -} - -// MockOutput provides a buffer where the content will be written -func MockOutput(output *bytes.Buffer) MockOptions { - return func(fs *mockFileSystem) { - fs.output = output - } -} - -// MockWriteFileError makes the Write method (of the io.Writer returned by FileSystem.Create) return err -func MockWriteFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.writeFileError = err - } -} - -// MockCloseFileError makes the Write method (of the io.Writer returned by FileSystem.Create) return err -func MockCloseFileError(err error) MockOptions { - return func(fs *mockFileSystem) { - fs.closeFileError = err - } -} - -// Exists implements FileSystem.Exists -func (fs mockFileSystem) Exists(path string) (bool, error) { - if fs.existsError != nil { - return false, fileExistsError{path, fs.existsError} - } - - return fs.exists(path), nil -} - -// Open implements FileSystem.Open -func (fs mockFileSystem) Open(path string) (io.ReadCloser, error) { - if fs.openFileError != nil { - return nil, openFileError{path, fs.openFileError} - } - - if fs.input == nil { - fs.input = bytes.NewBufferString("Hello world!") - } - - return &mockReadFile{path, fs.input, fs.readFileError, fs.closeFileError}, nil -} - -// Create implements FileSystem.Create -func (fs mockFileSystem) Create(path string) (io.Writer, error) { - if fs.createDirError != nil { - return nil, createDirectoryError{path, fs.createDirError} - } - - if fs.createFileError != nil { - return nil, createFileError{path, fs.createFileError} - } - - return &mockWriteFile{path, fs.output, fs.writeFileError, fs.closeFileError}, nil -} - -// mockReadFile implements io.Reader mocking a readFile for tests -type mockReadFile struct { - path string - input *bytes.Buffer - readFileError error - closeFileError error -} - -// Read implements io.Reader.ReadCloser -func (f *mockReadFile) Read(content []byte) (n int, err error) { - if f.readFileError != nil { - return 0, readFileError{path: f.path, err: f.readFileError} - } - - return f.input.Read(content) -} - -// Read implements io.Reader.ReadCloser -func (f *mockReadFile) Close() error { - if f.closeFileError != nil { - return closeFileError{path: f.path, err: f.closeFileError} - } - - return nil -} - -// mockWriteFile implements io.Writer mocking a writeFile for tests -type mockWriteFile struct { - path string - content *bytes.Buffer - writeFileError error - closeFileError error -} - -// Write implements io.Writer.Write -func (f *mockWriteFile) Write(content []byte) (n int, err error) { - defer func() { - if err == nil && f.closeFileError != nil { - err = closeFileError{f.path, f.closeFileError} - } - }() - - if f.writeFileError != nil { - return 0, writeFileError{f.path, f.writeFileError} - } - - return f.content.Write(content) -} diff --git a/pkg/plugins/internal/kubebuilder/filesystem/mock_test.go b/pkg/plugins/internal/kubebuilder/filesystem/mock_test.go deleted file mode 100644 index e9e7d58c..00000000 --- a/pkg/plugins/internal/kubebuilder/filesystem/mock_test.go +++ /dev/null @@ -1,448 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package filesystem - -import ( - "bytes" - "errors" - "path/filepath" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -//nolint:dupl -var _ = Describe("MockFileSystem", func() { - var ( - fsi FileSystem - fs mockFileSystem - ok bool - options []MockOptions - testErr = errors.New("test error") - ) - - JustBeforeEach(func() { - fsi = NewMock(options...) - fs, ok = fsi.(mockFileSystem) - }) - - Context("when using no options", func() { - BeforeEach(func() { - options = make([]MockOptions, 0) - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockPath", func() { - var filePath = filepath.Join("path", "to", "file") - - BeforeEach(func() { - options = []MockOptions{MockPath(filePath)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should save the provided path", func() { - Expect(fs.path).To(Equal(filePath)) - }) - }) - - Context("when using MockExists", func() { - BeforeEach(func() { - options = []MockOptions{MockExists(func(_ string) bool { return true })} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeTrue()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockExistsError", func() { - BeforeEach(func() { - options = []MockOptions{MockExistsError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should error when calling Exists", func() { - _, err := fsi.Exists("") - Expect(err).To(MatchError(testErr)) - Expect(IsFileExistsError(err)).To(BeTrue()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockOpenFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockOpenFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should error when calling Open", func() { - _, err := fsi.Open("") - Expect(err).To(MatchError(testErr)) - Expect(IsOpenFileError(err)).To(BeTrue()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockCreateDirError", func() { - BeforeEach(func() { - options = []MockOptions{MockCreateDirError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should error when calling Create", func() { - _, err := fsi.Create("") - Expect(err).To(MatchError(testErr)) - Expect(IsCreateDirectoryError(err)).To(BeTrue()) - }) - }) - - Context("when using MockCreateFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockCreateFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should error when calling Create", func() { - _, err := fsi.Create("") - Expect(err).To(MatchError(testErr)) - Expect(IsCreateFileError(err)).To(BeTrue()) - }) - }) - - Context("when using MockInput", func() { - var ( - input *bytes.Buffer - fileContent = []byte("Hello world!") - ) - - BeforeEach(func() { - input = bytes.NewBufferString("Hello world!") - options = []MockOptions{MockInput(input)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files and the content to be accessible", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - output := make([]byte, len(fileContent)) - n, err := f.Read(output) - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(len(fileContent))) - Expect(output).To(Equal(fileContent)) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockReadFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockReadFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should error when calling Open().Read", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - output := make([]byte, 0) - _, err = f.Read(output) - Expect(err).To(MatchError(testErr)) - Expect(IsReadFileError(err)).To(BeTrue()) - }) - - It("should create writable files", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - }) - - Context("when using MockOutput", func() { - var ( - output bytes.Buffer - fileContent = []byte("Hello world!") - ) - - BeforeEach(func() { - options = []MockOptions{MockOutput(&output)} - output.Reset() - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should create writable files and the content should be accesible", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - n, err := f.Write(fileContent) - Expect(err).NotTo(HaveOccurred()) - Expect(n).To(Equal(len(fileContent))) - Expect(output.Bytes()).To(Equal(fileContent)) - }) - }) - - Context("when using MockWriteFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockWriteFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should open readable files", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Read([]byte("")) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should error when calling Create().Write", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).To(MatchError(testErr)) - Expect(IsWriteFileError(err)).To(BeTrue()) - }) - }) - - Context("when using MockCloseFileError", func() { - BeforeEach(func() { - options = []MockOptions{MockCloseFileError(testErr)} - }) - - It("should be a mockFileSystem instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should claim that files don't exist", func() { - exists, err := fsi.Exists("") - Expect(err).NotTo(HaveOccurred()) - Expect(exists).To(BeFalse()) - }) - - It("should error when calling Open().Close", func() { - f, err := fsi.Open("") - Expect(err).NotTo(HaveOccurred()) - - err = f.Close() - Expect(err).To(MatchError(testErr)) - Expect(IsCloseFileError(err)).To(BeTrue()) - }) - - It("should error when calling Create().Write", func() { - f, err := fsi.Create("") - Expect(err).NotTo(HaveOccurred()) - - _, err = f.Write([]byte("")) - Expect(err).To(MatchError(testErr)) - Expect(IsCloseFileError(err)).To(BeTrue()) - }) - }) -}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/errors.go b/pkg/plugins/internal/kubebuilder/machinery/errors.go index faba57a1..af015ac3 100644 --- a/pkg/plugins/internal/kubebuilder/machinery/errors.go +++ b/pkg/plugins/internal/kubebuilder/machinery/errors.go @@ -17,58 +17,129 @@ limitations under the License. package machinery import ( - "errors" "fmt" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) // This file contains the errors returned by the scaffolding machinery -// They are not exported as they should not be created outside of this package -// Exported functions are provided to check which kind of error was returned +// They are exported to be able to check which kind of error was returned -// fileAlreadyExistsError is returned if the file is expected not to exist but it does -type fileAlreadyExistsError struct { - path string +// ValidateError is a wrapper error that will be used for errors returned by RequiresValidation.Validate +type ValidateError struct { + error } -// Error implements error interface -func (e fileAlreadyExistsError) Error() string { - return fmt.Sprintf("failed to create %s: file already exists", e.path) +// Unwrap implements Wrapper interface +func (e ValidateError) Unwrap() error { + return e.error +} + +// SetTemplateDefaultsError is a wrapper error that will be used for errors returned by Template.SetTemplateDefaults +type SetTemplateDefaultsError struct { + error } -// IsFileAlreadyExistsError checks if the returned error is because the file already existed when expected not to -func IsFileAlreadyExistsError(err error) bool { - return errors.As(err, &fileAlreadyExistsError{}) +// Unwrap implements Wrapper interface +func (e SetTemplateDefaultsError) Unwrap() error { + return e.error } -// modelAlreadyExistsError is returned if the file is expected not to exist but a previous model does -type modelAlreadyExistsError struct { +// ExistsFileError is a wrapper error that will be used for errors when checking for a file existence +type ExistsFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e ExistsFileError) Unwrap() error { + return e.error +} + +// OpenFileError is a wrapper error that will be used for errors when opening a file +type OpenFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e OpenFileError) Unwrap() error { + return e.error +} + +// CreateDirectoryError is a wrapper error that will be used for errors when creating a directory +type CreateDirectoryError struct { + error +} + +// Unwrap implements Wrapper interface +func (e CreateDirectoryError) Unwrap() error { + return e.error +} + +// CreateFileError is a wrapper error that will be used for errors when creating a file +type CreateFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e CreateFileError) Unwrap() error { + return e.error +} + +// ReadFileError is a wrapper error that will be used for errors when reading a file +type ReadFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e ReadFileError) Unwrap() error { + return e.error +} + +// WriteFileError is a wrapper error that will be used for errors when writing a file +type WriteFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e WriteFileError) Unwrap() error { + return e.error +} + +// CloseFileError is a wrapper error that will be used for errors when closing a file +type CloseFileError struct { + error +} + +// Unwrap implements Wrapper interface +func (e CloseFileError) Unwrap() error { + return e.error +} + +// ModelAlreadyExistsError is returned if the file is expected not to exist but a previous model does +type ModelAlreadyExistsError struct { path string } // Error implements error interface -func (e modelAlreadyExistsError) Error() string { +func (e ModelAlreadyExistsError) Error() string { return fmt.Sprintf("failed to create %s: model already exists", e.path) } -// IsModelAlreadyExistsError checks if the returned error is because the model already existed when expected not to -func IsModelAlreadyExistsError(err error) bool { - return errors.As(err, &modelAlreadyExistsError{}) -} - -// unknownIfExistsActionError is returned if the if-exists-action is unknown -type unknownIfExistsActionError struct { +// UnknownIfExistsActionError is returned if the if-exists-action is unknown +type UnknownIfExistsActionError struct { path string - ifExistsAction file.IfExistsAction + ifExistsAction IfExistsAction } // Error implements error interface -func (e unknownIfExistsActionError) Error() string { +func (e UnknownIfExistsActionError) Error() string { return fmt.Sprintf("unknown behavior if file exists (%d) for %s", e.ifExistsAction, e.path) } -// IsUnknownIfExistsActionError checks if the returned error is because the if-exists-action is unknown -func IsUnknownIfExistsActionError(err error) bool { - return errors.As(err, &unknownIfExistsActionError{}) +// FileAlreadyExistsError is returned if the file is expected not to exist but it does +type FileAlreadyExistsError struct { + path string +} + +// Error implements error interface +func (e FileAlreadyExistsError) Error() string { + return fmt.Sprintf("failed to create %s: file already exists", e.path) } diff --git a/pkg/plugins/internal/kubebuilder/machinery/errors_test.go b/pkg/plugins/internal/kubebuilder/machinery/errors_test.go index 3d6b8ce1..2c4d682e 100644 --- a/pkg/plugins/internal/kubebuilder/machinery/errors_test.go +++ b/pkg/plugins/internal/kubebuilder/machinery/errors_test.go @@ -27,39 +27,29 @@ import ( var _ = Describe("Errors", func() { var ( - path = filepath.Join("path", "to", "file") - err = errors.New("test error") - fileAlreadyExistsErr = fileAlreadyExistsError{path} - modelAlreadyExistsErr = modelAlreadyExistsError{path} - unknownIfExistsActionErr = unknownIfExistsActionError{path, -1} + path = filepath.Join("path", "to", "file") + testErr = errors.New("test error") ) - DescribeTable("IsXxxxError should return true for themselves and false for the rest", - func(f func(error) bool, itself error, rest ...error) { - Expect(f(itself)).To(BeTrue()) - for _, err := range rest { - Expect(f(err)).To(BeFalse()) - } - }, - Entry("file exists", IsFileAlreadyExistsError, fileAlreadyExistsErr, - err, modelAlreadyExistsErr, unknownIfExistsActionErr), - Entry("model exists", IsModelAlreadyExistsError, modelAlreadyExistsErr, - err, fileAlreadyExistsErr, unknownIfExistsActionErr), - Entry("unknown if exists action", IsUnknownIfExistsActionError, unknownIfExistsActionErr, - err, fileAlreadyExistsErr, modelAlreadyExistsErr), - ) - - DescribeTable("should contain the wrapped error and error message", + DescribeTable("should contain the wrapped error", func(err error) { - Expect(err).To(MatchError(err)) - Expect(err.Error()).To(ContainSubstring(err.Error())) + Expect(errors.Is(err, testErr)).To(BeTrue()) }, + Entry("for validate errors", ValidateError{testErr}), + Entry("for set template defaults errors", SetTemplateDefaultsError{testErr}), + Entry("for file existence errors", ExistsFileError{testErr}), + Entry("for file opening errors", OpenFileError{testErr}), + Entry("for directory creation errors", CreateDirectoryError{testErr}), + Entry("for file creation errors", CreateFileError{testErr}), + Entry("for file reading errors", ReadFileError{testErr}), + Entry("for file writing errors", WriteFileError{testErr}), + Entry("for file closing errors", CloseFileError{testErr}), ) // NOTE: the following test increases coverage It("should print a descriptive error message", func() { - Expect(fileAlreadyExistsErr.Error()).To(ContainSubstring("file already exists")) - Expect(modelAlreadyExistsErr.Error()).To(ContainSubstring("model already exists")) - Expect(unknownIfExistsActionErr.Error()).To(ContainSubstring("unknown behavior if file exists")) + Expect(ModelAlreadyExistsError{path}.Error()).To(ContainSubstring("model already exists")) + Expect(UnknownIfExistsActionError{path, -1}.Error()).To(ContainSubstring("unknown behavior if file exists")) + Expect(FileAlreadyExistsError{path}.Error()).To(ContainSubstring("file already exists")) }) }) diff --git a/pkg/plugins/internal/kubebuilder/machinery/file.go b/pkg/plugins/internal/kubebuilder/machinery/file.go new file mode 100644 index 00000000..bf053d14 --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/file.go @@ -0,0 +1,43 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +// IfExistsAction determines what to do if the scaffold file already exists +type IfExistsAction int + +const ( + // SkipFile skips the file and moves to the next one + SkipFile IfExistsAction = iota + + // Error returns an error and stops processing + Error + + // OverwriteFile truncates and overwrites the existing file + OverwriteFile +) + +// File describes a file that will be written +type File struct { + // Path is the file to write + Path string + + // Contents is the generated output + Contents string + + // IfExistsAction determines what to do if the file exists + IfExistsAction IfExistsAction +} diff --git a/pkg/plugins/internal/kubebuilder/filesystem/filesystem_suite_test.go b/pkg/plugins/internal/kubebuilder/machinery/filesystem.go similarity index 76% rename from pkg/plugins/internal/kubebuilder/filesystem/filesystem_suite_test.go rename to pkg/plugins/internal/kubebuilder/machinery/filesystem.go index 47302470..f28cfb8b 100644 --- a/pkg/plugins/internal/kubebuilder/filesystem/filesystem_suite_test.go +++ b/pkg/plugins/internal/kubebuilder/machinery/filesystem.go @@ -14,16 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package filesystem +package machinery import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/spf13/afero" ) -func TestScaffold(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Filesystem suite") +// Filesystem abstracts the underlying disk for scaffolding +type Filesystem struct { + FS afero.Fs } diff --git a/pkg/plugins/internal/kubebuilder/machinery/funcmap.go b/pkg/plugins/internal/kubebuilder/machinery/funcmap.go new file mode 100644 index 00000000..ac25e272 --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/funcmap.go @@ -0,0 +1,47 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + "fmt" + "hash/fnv" + "strings" + "text/template" +) + +// DefaultFuncMap returns the default template.FuncMap for rendering the template. +func DefaultFuncMap() template.FuncMap { + return template.FuncMap{ + "title": strings.Title, + "lower": strings.ToLower, + "isEmptyStr": isEmptyString, + "hashFNV": hashFNV, + } +} + +// isEmptyString returns whether the string is empty +func isEmptyString(s string) bool { + return s == "" +} + +// hashFNV will generate a random string useful for generating a unique string +func hashFNV(s string) string { + hasher := fnv.New32a() + // Hash.Write never returns an error + _, _ = hasher.Write([]byte(s)) + return fmt.Sprintf("%x", hasher.Sum(nil)) +} diff --git a/pkg/plugins/internal/kubebuilder/machinery/funcmap_test.go b/pkg/plugins/internal/kubebuilder/machinery/funcmap_test.go new file mode 100644 index 00000000..7bb33df4 --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/funcmap_test.go @@ -0,0 +1,45 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("funcmap functions", func() { + Context("isEmptyString", func() { + It("should return true for empty strings", func() { + Expect(isEmptyString("")).To(BeTrue()) + }) + + DescribeTable("should return false for any other string", + func(str string) { Expect(isEmptyString(str)).To(BeFalse()) }, + Entry(`for "a"`, "a"), + Entry(`for "1"`, "1"), + Entry(`for "-"`, "-"), + Entry(`for "."`, "."), + ) + }) + + Context("hashFNV", func() { + It("should hash the input", func() { + Expect(hashFNV("test")).To(Equal("afd071e5")) + }) + }) +}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/injector.go b/pkg/plugins/internal/kubebuilder/machinery/injector.go new file mode 100644 index 00000000..5675a8a9 --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/injector.go @@ -0,0 +1,66 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +// injector is used to inject certain fields to file templates. +type injector struct { + // config stores the project configuration. + config config.Config + + // boilerplate is the copyright comment added at the top of scaffolded files. + boilerplate string + + // resource contains the information of the API that is being scaffolded. + resource *resource.Resource +} + +// injectInto injects fields from the universe into the builder +func (i injector) injectInto(builder Builder) { + // Inject project configuration + if i.config != nil { + if builderWithDomain, hasDomain := builder.(HasDomain); hasDomain { + builderWithDomain.InjectDomain(i.config.GetDomain()) + } + if builderWithRepository, hasRepository := builder.(HasRepository); hasRepository { + builderWithRepository.InjectRepository(i.config.GetRepository()) + } + if builderWithProjectName, hasProjectName := builder.(HasProjectName); hasProjectName { + builderWithProjectName.InjectProjectName(i.config.GetProjectName()) + } + if builderWithMultiGroup, hasMultiGroup := builder.(HasMultiGroup); hasMultiGroup { + builderWithMultiGroup.InjectMultiGroup(i.config.IsMultiGroup()) + } + if builderWithComponentConfig, hasComponentConfig := builder.(HasComponentConfig); hasComponentConfig { + builderWithComponentConfig.InjectComponentConfig(i.config.IsComponentConfig()) + } + } + // Inject boilerplate + if builderWithBoilerplate, hasBoilerplate := builder.(HasBoilerplate); hasBoilerplate { + builderWithBoilerplate.InjectBoilerplate(i.boilerplate) + } + // Inject resource + if i.resource != nil { + if builderWithResource, hasResource := builder.(HasResource); hasResource { + builderWithResource.InjectResource(i.resource) + } + } +} diff --git a/pkg/plugins/internal/kubebuilder/machinery/injector_test.go b/pkg/plugins/internal/kubebuilder/machinery/injector_test.go new file mode 100644 index 00000000..7c5c3038 --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/injector_test.go @@ -0,0 +1,295 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +type templateBase struct { + path string + ifExistsAction IfExistsAction +} + +func (t templateBase) GetPath() string { + return t.path +} + +func (t templateBase) GetIfExistsAction() IfExistsAction { + return t.ifExistsAction +} + +type templateWithDomain struct { + templateBase + domain string +} + +func (t *templateWithDomain) InjectDomain(domain string) { + t.domain = domain +} + +type templateWithRepository struct { + templateBase + repository string +} + +func (t *templateWithRepository) InjectRepository(repository string) { + t.repository = repository +} + +type templateWithProjectName struct { + templateBase + projectName string +} + +func (t *templateWithProjectName) InjectProjectName(projectName string) { + t.projectName = projectName +} + +type templateWithMultiGroup struct { + templateBase + multiGroup bool +} + +func (t *templateWithMultiGroup) InjectMultiGroup(multiGroup bool) { + t.multiGroup = multiGroup +} + +type templateWithComponentConfig struct { + templateBase + componentConfig bool +} + +func (t *templateWithComponentConfig) InjectComponentConfig(componentConfig bool) { + t.componentConfig = componentConfig +} + +type templateWithBoilerplate struct { + templateBase + boilerplate string +} + +func (t *templateWithBoilerplate) InjectBoilerplate(boilerplate string) { + t.boilerplate = boilerplate +} + +type templateWithResource struct { + templateBase + resource *resource.Resource +} + +func (t *templateWithResource) InjectResource(res *resource.Resource) { + t.resource = res +} + +var _ = Describe("injector", func() { + var tmp = templateBase{ + path: "my/path/to/file", + ifExistsAction: Error, + } + + Context("injectInto", func() { + Context("Config", func() { + var c config.Config + + BeforeEach(func() { + c = cfgv3.New() + }) + + Context("Domain", func() { + var template *templateWithDomain + + BeforeEach(func() { + template = &templateWithDomain{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.domain).To(Equal("")) + }) + + It("should not inject anything if the config doesn't have a domain set", func() { + injector{config: c}.injectInto(template) + Expect(template.domain).To(Equal("")) + }) + + It("should inject if the config has a domain set", func() { + const domain = "my.domain" + Expect(c.SetDomain(domain)).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.domain).To(Equal(domain)) + }) + }) + + Context("Repository", func() { + var template *templateWithRepository + + BeforeEach(func() { + template = &templateWithRepository{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.repository).To(Equal("")) + }) + + It("should not inject anything if the config doesn't have a repository set", func() { + injector{config: c}.injectInto(template) + Expect(template.repository).To(Equal("")) + }) + + It("should inject if the config has a repository set", func() { + const repo = "test" + Expect(c.SetRepository(repo)).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.repository).To(Equal(repo)) + }) + }) + + Context("Project name", func() { + var template *templateWithProjectName + + BeforeEach(func() { + template = &templateWithProjectName{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.projectName).To(Equal("")) + }) + + It("should not inject anything if the config doesn't have a project name set", func() { + injector{config: c}.injectInto(template) + Expect(template.projectName).To(Equal("")) + }) + + It("should inject if the config has a project name set", func() { + const projectName = "my project" + Expect(c.SetProjectName(projectName)).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.projectName).To(Equal(projectName)) + }) + }) + + Context("Multi-group", func() { + var template *templateWithMultiGroup + + BeforeEach(func() { + template = &templateWithMultiGroup{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.multiGroup).To(BeFalse()) + }) + + It("should not set the flag if the config doesn't have the multi-group flag set", func() { + injector{config: c}.injectInto(template) + Expect(template.multiGroup).To(BeFalse()) + }) + + It("should set the flag if the config has the multi-group flag set", func() { + Expect(c.SetMultiGroup()).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.multiGroup).To(BeTrue()) + }) + }) + + Context("Component config", func() { + var template *templateWithComponentConfig + + BeforeEach(func() { + template = &templateWithComponentConfig{templateBase: tmp} + }) + + It("should not inject anything if the config is nil", func() { + injector{}.injectInto(template) + Expect(template.componentConfig).To(BeFalse()) + }) + + It("should not set the flag if the config doesn't have the component config flag set", func() { + injector{config: c}.injectInto(template) + Expect(template.componentConfig).To(BeFalse()) + }) + + It("should set the flag if the config has the component config flag set", func() { + Expect(c.SetComponentConfig()).To(Succeed()) + + injector{config: c}.injectInto(template) + Expect(template.componentConfig).To(BeTrue()) + }) + }) + }) + + Context("Boilerplate", func() { + var template *templateWithBoilerplate + + BeforeEach(func() { + template = &templateWithBoilerplate{templateBase: tmp} + }) + + It("should not inject anything if no boilerplate was set", func() { + injector{}.injectInto(template) + Expect(template.boilerplate).To(Equal("")) + }) + + It("should inject if the a boilerplate was set", func() { + const boilerplate = `Copyright "The Kubernetes Authors"` + + injector{boilerplate: boilerplate}.injectInto(template) + Expect(template.boilerplate).To(Equal(boilerplate)) + }) + }) + + Context("Resource", func() { + var template *templateWithResource + + BeforeEach(func() { + template = &templateWithResource{templateBase: tmp} + }) + + It("should not inject anything if the resource is nil", func() { + injector{}.injectInto(template) + Expect(template.resource).To(BeNil()) + }) + + It("should inject if the config has a domain set", func() { + var res = &resource.Resource{ + GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }, + } + + injector{resource: res}.injectInto(template) + Expect(template.resource).To(Equal(res)) + }) + + }) + }) +}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/interfaces.go b/pkg/plugins/internal/kubebuilder/machinery/interfaces.go new file mode 100644 index 00000000..73837dd8 --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/interfaces.go @@ -0,0 +1,104 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + "text/template" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +// Builder defines the basic methods that any file builder must implement +type Builder interface { + // GetPath returns the path to the file location + GetPath() string + // GetIfExistsAction returns the behavior when creating a file that already exists + GetIfExistsAction() IfExistsAction +} + +// RequiresValidation is a file builder that requires validation +type RequiresValidation interface { + Builder + // Validate returns true if the template has valid values + Validate() error +} + +// Template is file builder based on a file template +type Template interface { + Builder + // GetBody returns the template body + GetBody() string + // SetTemplateDefaults sets the default values for templates + SetTemplateDefaults() error +} + +// Inserter is a file builder that inserts code fragments in marked positions +type Inserter interface { + Builder + // GetMarkers returns the different markers where code fragments will be inserted + GetMarkers() []Marker + // GetCodeFragments returns a map that binds markers to code fragments + GetCodeFragments() CodeFragmentsMap +} + +// HasDomain allows the domain to be used on a template +type HasDomain interface { + // InjectDomain sets the template domain + InjectDomain(string) +} + +// HasRepository allows the repository to be used on a template +type HasRepository interface { + // InjectRepository sets the template repository + InjectRepository(string) +} + +// HasProjectName allows a project name to be used on a template. +type HasProjectName interface { + // InjectProjectName sets the template project name. + InjectProjectName(string) +} + +// HasMultiGroup allows the multi-group flag to be used on a template +type HasMultiGroup interface { + // InjectMultiGroup sets the template multi-group flag + InjectMultiGroup(bool) +} + +// HasComponentConfig allows the component-config flag to be used on a template +type HasComponentConfig interface { + // InjectComponentConfig sets the template component-config flag + InjectComponentConfig(bool) +} + +// HasBoilerplate allows a boilerplate to be used on a template +type HasBoilerplate interface { + // InjectBoilerplate sets the template boilerplate + InjectBoilerplate(string) +} + +// HasResource allows a resource to be used on a template +type HasResource interface { + // InjectResource sets the template resource + InjectResource(*resource.Resource) +} + +// UseCustomFuncMap allows a template to use a custom template.FuncMap instead of the default FuncMap. +type UseCustomFuncMap interface { + // GetFuncMap returns a custom FuncMap. + GetFuncMap() template.FuncMap +} diff --git a/pkg/plugins/internal/kubebuilder/machinery/marker.go b/pkg/plugins/internal/kubebuilder/machinery/marker.go new file mode 100644 index 00000000..e048615c --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/marker.go @@ -0,0 +1,71 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + "fmt" + "path/filepath" + "strings" +) + +const prefix = "+kubebuilder:scaffold:" + +var commentsByExt = map[string]string{ + ".go": "//", + ".yaml": "#", + ".yml": "#", + // When adding additional file extensions, update also the NewMarkerFor documentation and error +} + +// Marker represents a machine-readable comment that will be used for scaffolding purposes +type Marker struct { + comment string + value string +} + +// NewMarkerFor creates a new marker customized for the specific file +// Supported file extensions: .go, .yaml, .yml +func NewMarkerFor(path string, value string) Marker { + ext := filepath.Ext(path) + if comment, found := commentsByExt[ext]; found { + return Marker{comment, value} + } + + extensions := make([]string, 0, len(commentsByExt)) + for extension := range commentsByExt { + extensions = append(extensions, fmt.Sprintf("%q", extension)) + } + panic(fmt.Errorf("unknown file extension: '%s', expected one of: %s", ext, strings.Join(extensions, ", "))) +} + +// String implements Stringer +func (m Marker) String() string { + return m.comment + prefix + m.value +} + +// EqualsLine compares a marker with a string representation to check if they are the same marker +func (m Marker) EqualsLine(line string) bool { + line = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), m.comment)) + return line == prefix+m.value +} + +// CodeFragments represents a set of code fragments +// A code fragment is a piece of code provided as a Go string, it may have multiple lines +type CodeFragments []string + +// CodeFragmentsMap binds Markers and CodeFragments together +type CodeFragmentsMap map[Marker]CodeFragments diff --git a/pkg/plugins/internal/kubebuilder/machinery/marker_test.go b/pkg/plugins/internal/kubebuilder/machinery/marker_test.go new file mode 100644 index 00000000..2f4468ff --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/marker_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("NerMarkerFor", func() { + DescribeTable("should create valid markers for known extensions", + func(path, comment string) { Expect(NewMarkerFor(path, "").comment).To(Equal(comment)) }, + Entry("for go files", "file.go", "//"), + Entry("for yaml files", "file.yaml", "#"), + Entry("for yaml files (short version)", "file.yml", "#"), + ) + + It("should panic for unknown extensions", func() { + // testing panics require to use a function with no arguments + Expect(func() { NewMarkerFor("file.unkownext", "") }).To(Panic()) + }) +}) + +var _ = Describe("Marker", func() { + Context("String", func() { + DescribeTable("should return the right string representation", + func(marker Marker, str string) { Expect(marker.String()).To(Equal(str)) }, + Entry("for go files", Marker{comment: "//", value: "test"}, "//+kubebuilder:scaffold:test"), + Entry("for yaml files", Marker{comment: "#", value: "test"}, "#+kubebuilder:scaffold:test"), + ) + }) +}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/mixins.go b/pkg/plugins/internal/kubebuilder/machinery/mixins.go new file mode 100644 index 00000000..9529e687 --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/mixins.go @@ -0,0 +1,153 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +// PathMixin provides file builders with a path field +type PathMixin struct { + // Path is the of the file + Path string +} + +// GetPath implements Builder +func (t *PathMixin) GetPath() string { + return t.Path +} + +// IfExistsActionMixin provides file builders with a if-exists-action field +type IfExistsActionMixin struct { + // IfExistsAction determines what to do if the file exists + IfExistsAction IfExistsAction +} + +// GetIfExistsAction implements Builder +func (t *IfExistsActionMixin) GetIfExistsAction() IfExistsAction { + return t.IfExistsAction +} + +// TemplateMixin is the mixin that should be embedded in Template builders +type TemplateMixin struct { + PathMixin + IfExistsActionMixin + + // TemplateBody is the template body to execute + TemplateBody string +} + +// GetBody implements Template +func (t *TemplateMixin) GetBody() string { + return t.TemplateBody +} + +// InserterMixin is the mixin that should be embedded in Inserter builders +type InserterMixin struct { + PathMixin +} + +// GetIfExistsAction implements Builder +func (t *InserterMixin) GetIfExistsAction() IfExistsAction { + // Inserter builders always need to overwrite previous files + return OverwriteFile +} + +// DomainMixin provides templates with a injectable domain field +type DomainMixin struct { + // Domain is the domain for the APIs + Domain string +} + +// InjectDomain implements HasDomain +func (m *DomainMixin) InjectDomain(domain string) { + if m.Domain == "" { + m.Domain = domain + } +} + +// RepositoryMixin provides templates with a injectable repository field +type RepositoryMixin struct { + // Repo is the go project package path + Repo string +} + +// InjectRepository implements HasRepository +func (m *RepositoryMixin) InjectRepository(repository string) { + if m.Repo == "" { + m.Repo = repository + } +} + +// ProjectNameMixin provides templates with an injectable project name field. +type ProjectNameMixin struct { + ProjectName string +} + +// InjectProjectName implements HasProjectName. +func (m *ProjectNameMixin) InjectProjectName(projectName string) { + if m.ProjectName == "" { + m.ProjectName = projectName + } +} + +// MultiGroupMixin provides templates with a injectable multi-group flag field +type MultiGroupMixin struct { + // MultiGroup is the multi-group flag + MultiGroup bool +} + +// InjectMultiGroup implements HasMultiGroup +func (m *MultiGroupMixin) InjectMultiGroup(flag bool) { + m.MultiGroup = flag +} + +// ComponentConfigMixin provides templates with a injectable component-config flag field +type ComponentConfigMixin struct { + // ComponentConfig is the component-config flag + ComponentConfig bool +} + +// InjectComponentConfig implements HasComponentConfig +func (m *ComponentConfigMixin) InjectComponentConfig(flag bool) { + m.ComponentConfig = flag +} + +// BoilerplateMixin provides templates with a injectable boilerplate field +type BoilerplateMixin struct { + // Boilerplate is the contents of a Boilerplate go header file + Boilerplate string +} + +// InjectBoilerplate implements HasBoilerplate +func (m *BoilerplateMixin) InjectBoilerplate(boilerplate string) { + if m.Boilerplate == "" { + m.Boilerplate = boilerplate + } +} + +// ResourceMixin provides templates with a injectable resource field +type ResourceMixin struct { + Resource *resource.Resource +} + +// InjectResource implements HasResource +func (m *ResourceMixin) InjectResource(res *resource.Resource) { + if m.Resource == nil { + m.Resource = res + } +} diff --git a/pkg/plugins/internal/kubebuilder/machinery/mixins_test.go b/pkg/plugins/internal/kubebuilder/machinery/mixins_test.go new file mode 100644 index 00000000..944e4c0c --- /dev/null +++ b/pkg/plugins/internal/kubebuilder/machinery/mixins_test.go @@ -0,0 +1,188 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package machinery + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +type mockTemplate struct { + TemplateMixin + DomainMixin + RepositoryMixin + ProjectNameMixin + MultiGroupMixin + ComponentConfigMixin + BoilerplateMixin + ResourceMixin +} + +type mockInserter struct { + // InserterMixin requires a different type because it collides with TemplateMixin + InserterMixin +} + +var _ = Describe("TemplateMixin", func() { + const ( + path = "path/to/file.go" + ifExistsAction = SkipFile + body = "content" + ) + + var tmp = mockTemplate{ + TemplateMixin: TemplateMixin{ + PathMixin: PathMixin{path}, + IfExistsActionMixin: IfExistsActionMixin{ifExistsAction}, + TemplateBody: body, + }, + } + + Context("GetPath", func() { + It("should return the path", func() { + Expect(tmp.GetPath()).To(Equal(path)) + }) + }) + + Context("GetIfExistsAction", func() { + It("should return the if-exists action", func() { + Expect(tmp.GetIfExistsAction()).To(Equal(ifExistsAction)) + }) + }) + + Context("GetBody", func() { + It("should return the body", func() { + Expect(tmp.GetBody()).To(Equal(body)) + }) + }) +}) + +var _ = Describe("InserterMixin", func() { + const path = "path/to/file.go" + + var tmp = mockInserter{ + InserterMixin: InserterMixin{ + PathMixin: PathMixin{path}, + }, + } + + Context("GetPath", func() { + It("should return the path", func() { + Expect(tmp.GetPath()).To(Equal(path)) + }) + }) + + Context("GetIfExistsAction", func() { + It("should return overwrite file always", func() { + Expect(tmp.GetIfExistsAction()).To(Equal(OverwriteFile)) + }) + }) +}) + +var _ = Describe("DomainMixin", func() { + const domain = "my.domain" + + var tmp = mockTemplate{} + + Context("InjectDomain", func() { + It("should inject the provided domain", func() { + tmp.InjectDomain(domain) + Expect(tmp.Domain).To(Equal(domain)) + }) + }) +}) + +var _ = Describe("RepositoryMixin", func() { + const repo = "test" + + var tmp = mockTemplate{} + + Context("InjectRepository", func() { + It("should inject the provided repository", func() { + tmp.InjectRepository(repo) + Expect(tmp.Repo).To(Equal(repo)) + }) + }) +}) + +var _ = Describe("ProjectNameMixin", func() { + const name = "my project" + + var tmp = mockTemplate{} + + Context("InjectProjectName", func() { + It("should inject the provided project name", func() { + tmp.InjectProjectName(name) + Expect(tmp.ProjectName).To(Equal(name)) + }) + }) +}) + +var _ = Describe("MultiGroupMixin", func() { + var tmp = mockTemplate{} + + Context("InjectMultiGroup", func() { + It("should inject the provided multi group flag", func() { + tmp.InjectMultiGroup(true) + Expect(tmp.MultiGroup).To(BeTrue()) + }) + }) +}) + +var _ = Describe("ComponentConfigMixin", func() { + var tmp = mockTemplate{} + + Context("InjectComponentConfig", func() { + It("should inject the provided component config flag", func() { + tmp.InjectComponentConfig(true) + Expect(tmp.ComponentConfig).To(BeTrue()) + }) + }) +}) + +var _ = Describe("BoilerplateMixin", func() { + const boilerplate = "Copyright" + + var tmp = mockTemplate{} + + Context("InjectBoilerplate", func() { + It("should inject the provided boilerplate", func() { + tmp.InjectBoilerplate(boilerplate) + Expect(tmp.Boilerplate).To(Equal(boilerplate)) + }) + }) +}) + +var _ = Describe("ResourceMixin", func() { + var res = &resource.Resource{GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }} + + var tmp = mockTemplate{} + + Context("InjectResource", func() { + It("should inject the provided resource", func() { + tmp.InjectResource(res) + Expect(tmp.Resource.GVK.IsEqualTo(res.GVK)).To(BeTrue()) + }) + }) +}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/scaffold.go b/pkg/plugins/internal/kubebuilder/machinery/scaffold.go index 05ea32c5..3b79104a 100644 --- a/pkg/plugins/internal/kubebuilder/machinery/scaffold.go +++ b/pkg/plugins/internal/kubebuilder/machinery/scaffold.go @@ -20,16 +20,23 @@ import ( "bufio" "bytes" "fmt" - "io/ioutil" + "os" "path/filepath" "strings" "text/template" + "github.com/spf13/afero" "golang.org/x/tools/imports" - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" - "github.com/joelanford/helm-operator/pkg/plugins/internal/kubebuilder/filesystem" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +const ( + createOrUpdate = os.O_WRONLY | os.O_CREATE | os.O_TRUNC + + defaultDirectoryPermission os.FileMode = 0700 + defaultFilePermission os.FileMode = 0600 ) var options = imports.Options{ @@ -40,73 +47,108 @@ var options = imports.Options{ } // Scaffold uses templates to scaffold new files -type Scaffold interface { - // Execute writes to disk the provided files - Execute(*model.Universe, ...file.Builder) error -} +type Scaffold struct { + // fs allows to mock the file system for tests + fs afero.Fs -// scaffold implements Scaffold interface -type scaffold struct { - // plugins is the list of plugins we should allow to transform our generated scaffolding - plugins []model.Plugin + // permissions for new directories and files + dirPerm os.FileMode + filePerm os.FileMode - // fs allows to mock the file system for tests - fs filesystem.FileSystem + // injector is used to provide several fields to the templates + injector injector } +// ScaffoldOption allows to provide optional arguments to the Scaffold +type ScaffoldOption func(*Scaffold) + // NewScaffold returns a new Scaffold with the provided plugins -func NewScaffold(plugins ...model.Plugin) Scaffold { - return &scaffold{ - plugins: plugins, - fs: filesystem.New(), +func NewScaffold(fs Filesystem, options ...ScaffoldOption) *Scaffold { + s := &Scaffold{ + fs: fs.FS, + dirPerm: defaultDirectoryPermission, + filePerm: defaultFilePermission, } + + for _, option := range options { + option(s) + } + + return s } -// Execute implements Scaffold.Execute -func (s *scaffold) Execute(universe *model.Universe, files ...file.Builder) error { - // Initialize the universe files - universe.Files = make(map[string]*file.File, len(files)) +// WithDirectoryPermissions sets the permissions for new directories +func WithDirectoryPermissions(dirPerm os.FileMode) ScaffoldOption { + return func(s *Scaffold) { + s.dirPerm = dirPerm + } +} - // Set the repo as the local prefix so that it knows how to group imports - if universe.Config != nil { - imports.LocalPrefix = universe.Config.GetRepository() +// WithFilePermissions sets the permissions for new files +func WithFilePermissions(filePerm os.FileMode) ScaffoldOption { + return func(s *Scaffold) { + s.filePerm = filePerm } +} - for _, f := range files { +// WithConfig provides the project configuration to the Scaffold +func WithConfig(cfg config.Config) ScaffoldOption { + return func(s *Scaffold) { + s.injector.config = cfg + + if cfg != nil && cfg.GetRepository() != "" { + imports.LocalPrefix = cfg.GetRepository() + } + } +} + +// WithBoilerplate provides the boilerplate to the Scaffold +func WithBoilerplate(boilerplate string) ScaffoldOption { + return func(s *Scaffold) { + s.injector.boilerplate = boilerplate + } +} + +// WithResource provides the resource to the Scaffold +func WithResource(resource *resource.Resource) ScaffoldOption { + return func(s *Scaffold) { + s.injector.resource = resource + } +} + +// Execute writes to disk the provided files +func (s *Scaffold) Execute(builders ...Builder) error { + // Initialize the files + files := make(map[string]*File, len(builders)) + + for _, builder := range builders { // Inject common fields - universe.InjectInto(f) + s.injector.injectInto(builder) // Validate file builders - if reqValFile, requiresValidation := f.(file.RequiresValidation); requiresValidation { - if err := reqValFile.Validate(); err != nil { - return file.NewValidateError(err) + if reqValBuilder, requiresValidation := builder.(RequiresValidation); requiresValidation { + if err := reqValBuilder.Validate(); err != nil { + return ValidateError{err} } } // Build models for Template builders - if t, isTemplate := f.(file.Template); isTemplate { - if err := s.buildFileModel(t, universe.Files); err != nil { + if t, isTemplate := builder.(Template); isTemplate { + if err := s.buildFileModel(t, files); err != nil { return err } } // Build models for Inserter builders - if i, isInserter := f.(file.Inserter); isInserter { - if err := s.updateFileModel(i, universe.Files); err != nil { + if i, isInserter := builder.(Inserter); isInserter { + if err := s.updateFileModel(i, files); err != nil { return err } } } - // Execute plugins - for _, plugin := range s.plugins { - if err := plugin.Pipe(universe); err != nil { - return model.NewPluginError(err) - } - } - // Persist the files to disk - for _, f := range universe.Files { + for _, f := range files { if err := s.writeFile(f); err != nil { return err } @@ -116,51 +158,60 @@ func (s *scaffold) Execute(universe *model.Universe, files ...file.Builder) erro } // buildFileModel scaffolds a single file -func (scaffold) buildFileModel(t file.Template, models map[string]*file.File) error { +func (Scaffold) buildFileModel(t Template, models map[string]*File) error { // Set the template default values - err := t.SetTemplateDefaults() - if err != nil { - return file.NewSetTemplateDefaultsError(err) + if err := t.SetTemplateDefaults(); err != nil { + return SetTemplateDefaultsError{err} } + path := t.GetPath() + // Handle already existing models - if _, found := models[t.GetPath()]; found { + if _, found := models[path]; found { switch t.GetIfExistsAction() { - case file.Skip: + case SkipFile: return nil - case file.Error: - return modelAlreadyExistsError{t.GetPath()} - case file.Overwrite: + case Error: + return ModelAlreadyExistsError{path} + case OverwriteFile: default: - return unknownIfExistsActionError{t.GetPath(), t.GetIfExistsAction()} + return UnknownIfExistsActionError{path, t.GetIfExistsAction()} } } - m := &file.File{ - Path: t.GetPath(), - IfExistsAction: t.GetIfExistsAction(), - } - b, err := doTemplate(t) if err != nil { return err } - m.Contents = string(b) - models[m.Path] = m + models[path] = &File{ + Path: path, + Contents: string(b), + IfExistsAction: t.GetIfExistsAction(), + } return nil } // doTemplate executes the template for a file using the input -func doTemplate(t file.Template) ([]byte, error) { - temp, err := newTemplate(t).Parse(t.GetBody()) - if err != nil { +func doTemplate(t Template) ([]byte, error) { + // Create a new template.Template using the type of the Template as the name + temp := template.New(fmt.Sprintf("%T", t)) + + // Set the function map to be used + fm := DefaultFuncMap() + if templateWithFuncMap, hasCustomFuncMap := t.(UseCustomFuncMap); hasCustomFuncMap { + fm = templateWithFuncMap.GetFuncMap() + } + temp.Funcs(fm) + + // Set the template body + if _, err := temp.Parse(t.GetBody()); err != nil { return nil, err } + // Execute the template out := &bytes.Buffer{} - err = temp.Execute(out, t) - if err != nil { + if err := temp.Execute(out, t); err != nil { return nil, err } b := out.Bytes() @@ -168,8 +219,8 @@ func doTemplate(t file.Template) ([]byte, error) { // TODO(adirio): move go-formatting to write step // gofmt the imports if filepath.Ext(t.GetPath()) == ".go" { - b, err = imports.Process(t.GetPath(), b, &options) - if err != nil { + var err error + if b, err = imports.Process(t.GetPath(), b, &options); err != nil { return nil, err } } @@ -177,18 +228,8 @@ func doTemplate(t file.Template) ([]byte, error) { return b, nil } -// newTemplate a new template with common functions -func newTemplate(t file.Template) *template.Template { - fm := file.DefaultFuncMap() - useFM, ok := t.(file.UseCustomFuncMap) - if ok { - fm = useFM.GetFuncMap() - } - return template.New(fmt.Sprintf("%T", t)).Funcs(fm) -} - // updateFileModel updates a single file -func (s scaffold) updateFileModel(i file.Inserter, models map[string]*file.File) error { +func (s Scaffold) updateFileModel(i Inserter, models map[string]*File) error { m, err := s.loadPreviousModel(i, models) if err != nil { return err @@ -223,19 +264,21 @@ func (s scaffold) updateFileModel(i file.Inserter, models map[string]*file.File) } m.Contents = string(formattedContent) - m.IfExistsAction = file.Overwrite + m.IfExistsAction = OverwriteFile models[m.Path] = m return nil } // loadPreviousModel gets the previous model from the models map or the actual file -func (s scaffold) loadPreviousModel(i file.Inserter, models map[string]*file.File) (*file.File, error) { +func (s Scaffold) loadPreviousModel(i Inserter, models map[string]*File) (*File, error) { + path := i.GetPath() + // Lets see if we already have a model for this file - if m, found := models[i.GetPath()]; found { + if m, found := models[path]; found { // Check if there is already an scaffolded file - exists, err := s.fs.Exists(i.GetPath()) + exists, err := afero.Exists(s.fs, path) if err != nil { - return nil, err + return nil, ExistsFileError{err} } // If there is a model but no scaffolded file we return the model @@ -245,52 +288,50 @@ func (s scaffold) loadPreviousModel(i file.Inserter, models map[string]*file.Fil // If both a model and a file are found, check which has preference switch m.IfExistsAction { - case file.Skip: + case SkipFile: // File has preference - fromFile, err := s.loadModelFromFile(i.GetPath()) + fromFile, err := s.loadModelFromFile(path) if err != nil { return m, nil } return fromFile, nil - case file.Error: + case Error: // Writing will result in an error, so we can return error now - return nil, fileAlreadyExistsError{i.GetPath()} - case file.Overwrite: + return nil, FileAlreadyExistsError{path} + case OverwriteFile: // Model has preference return m, nil default: - return nil, unknownIfExistsActionError{i.GetPath(), m.IfExistsAction} + return nil, UnknownIfExistsActionError{path, m.IfExistsAction} } } // There was no model - return s.loadModelFromFile(i.GetPath()) + return s.loadModelFromFile(path) } // loadModelFromFile gets the previous model from the actual file -func (s scaffold) loadModelFromFile(path string) (f *file.File, err error) { +func (s Scaffold) loadModelFromFile(path string) (f *File, err error) { reader, err := s.fs.Open(path) if err != nil { - return + return nil, OpenFileError{err} } defer func() { - closeErr := reader.Close() - if err == nil { - err = closeErr + if closeErr := reader.Close(); err == nil && closeErr != nil { + err = CloseFileError{closeErr} } }() - content, err := ioutil.ReadAll(reader) + content, err := afero.ReadAll(reader) if err != nil { - return + return nil, ReadFileError{err} } - f = &file.File{Path: path, Contents: string(content)} - return + return &File{Path: path, Contents: string(content)}, nil } // getValidCodeFragments obtains the code fragments from a file.Inserter -func getValidCodeFragments(i file.Inserter) file.CodeFragmentsMap { +func getValidCodeFragments(i Inserter) CodeFragmentsMap { // Get the code fragments codeFragments := i.GetCodeFragments() @@ -314,7 +355,7 @@ func getValidCodeFragments(i file.Inserter) file.CodeFragmentsMap { // filterExistingValues removes the single-line values that already exists // TODO: Add support for multi-line duplicate values -func filterExistingValues(content string, codeFragmentsMap file.CodeFragmentsMap) error { +func filterExistingValues(content string, codeFragmentsMap CodeFragmentsMap) error { scanner := bufio.NewScanner(strings.NewReader(content)) for scanner.Scan() { line := scanner.Text() @@ -335,7 +376,7 @@ func filterExistingValues(content string, codeFragmentsMap file.CodeFragmentsMap return nil } -func insertStrings(content string, codeFragmentsMap file.CodeFragmentsMap) ([]byte, error) { +func insertStrings(content string, codeFragmentsMap CodeFragmentsMap) ([]byte, error) { out := new(bytes.Buffer) scanner := bufio.NewScanner(strings.NewReader(content)) @@ -359,31 +400,44 @@ func insertStrings(content string, codeFragmentsMap file.CodeFragmentsMap) ([]by return out.Bytes(), nil } -func (s scaffold) writeFile(f *file.File) error { +func (s Scaffold) writeFile(f *File) (err error) { // Check if the file to write already exists - exists, err := s.fs.Exists(f.Path) + exists, err := afero.Exists(s.fs, f.Path) if err != nil { - return err + return ExistsFileError{err} } if exists { switch f.IfExistsAction { - case file.Overwrite: + case OverwriteFile: // By not returning, the file is written as if it didn't exist - case file.Skip: + case SkipFile: // By returning nil, the file is not written but the process will carry on return nil - case file.Error: + case Error: // By returning an error, the file is not written and the process will fail - return fileAlreadyExistsError{f.Path} + return FileAlreadyExistsError{f.Path} } } - writer, err := s.fs.Create(f.Path) + // Create the directory if needed + if err := s.fs.MkdirAll(filepath.Dir(f.Path), s.dirPerm); err != nil { + return CreateDirectoryError{err} + } + + // Create or truncate the file + writer, err := s.fs.OpenFile(f.Path, createOrUpdate, s.filePerm) if err != nil { - return err + return CreateFileError{err} } + defer func() { + if closeErr := writer.Close(); err == nil && closeErr != nil { + err = CloseFileError{err} + } + }() - _, err = writer.Write([]byte(f.Contents)) + if _, err := writer.Write([]byte(f.Contents)); err != nil { + return WriteFileError{err} + } - return err + return nil } diff --git a/pkg/plugins/internal/kubebuilder/machinery/scaffold_test.go b/pkg/plugins/internal/kubebuilder/machinery/scaffold_test.go index ffed12a7..96142e25 100644 --- a/pkg/plugins/internal/kubebuilder/machinery/scaffold_test.go +++ b/pkg/plugins/internal/kubebuilder/machinery/scaffold_test.go @@ -15,159 +15,174 @@ limitations under the License. package machinery import ( - "bytes" "errors" + "os" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "github.com/spf13/afero" - "github.com/joelanford/helm-operator/pkg/plugins/internal/kubebuilder/filesystem" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) var _ = Describe("Scaffold", func() { Describe("NewScaffold", func() { - var ( - si Scaffold - s *scaffold - ok bool - ) - - Context("when using no plugins", func() { - BeforeEach(func() { - si = NewScaffold() - s, ok = si.(*scaffold) - }) - - It("should be a scaffold instance", func() { - Expect(ok).To(BeTrue()) - }) - - It("should not have a nil fs", func() { - Expect(s.fs).NotTo(BeNil()) - }) - - It("should not have any plugin", func() { - Expect(len(s.plugins)).To(Equal(0)) - }) + It("should succeed for no option", func() { + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.injector.config).To(BeNil()) + Expect(s.injector.boilerplate).To(Equal("")) + Expect(s.injector.resource).To(BeNil()) }) - Context("when using one plugin", func() { - BeforeEach(func() { - si = NewScaffold(fakePlugin{}) - s, ok = si.(*scaffold) - }) + It("should succeed with directory permissions option", func() { + const dirPermissions os.FileMode = 0755 - It("should be a scaffold instance", func() { - Expect(ok).To(BeTrue()) - }) + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithDirectoryPermissions(dirPermissions)) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(dirPermissions)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.injector.config).To(BeNil()) + Expect(s.injector.boilerplate).To(Equal("")) + Expect(s.injector.resource).To(BeNil()) + }) - It("should not have a nil fs", func() { - Expect(s.fs).NotTo(BeNil()) - }) + It("should succeed with file permissions option", func() { + const filePermissions os.FileMode = 0755 - It("should have one plugin", func() { - Expect(len(s.plugins)).To(Equal(1)) - }) + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithFilePermissions(filePermissions)) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(filePermissions)) + Expect(s.injector.config).To(BeNil()) + Expect(s.injector.boilerplate).To(Equal("")) + Expect(s.injector.resource).To(BeNil()) }) - Context("when using several plugins", func() { - BeforeEach(func() { - si = NewScaffold(fakePlugin{}, fakePlugin{}, fakePlugin{}) - s, ok = si.(*scaffold) - }) + It("should succeed with config option", func() { + cfg := cfgv3.New() + + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithConfig(cfg)) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.injector.config).NotTo(BeNil()) + Expect(s.injector.config.GetVersion().Compare(cfgv3.Version)).To(Equal(0)) + Expect(s.injector.boilerplate).To(Equal("")) + Expect(s.injector.resource).To(BeNil()) + }) - It("should be a scaffold instance", func() { - Expect(ok).To(BeTrue()) - }) + It("should succeed with boilerplate option", func() { + const boilerplate = "Copyright" - It("should not have a nil fs", func() { - Expect(s.fs).NotTo(BeNil()) - }) + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithBoilerplate(boilerplate)) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.injector.config).To(BeNil()) + Expect(s.injector.boilerplate).To(Equal(boilerplate)) + Expect(s.injector.resource).To(BeNil()) + }) - It("should have several plugins", func() { - Expect(len(s.plugins)).To(Equal(3)) - }) + It("should succeed with resource option", func() { + var res = &resource.Resource{GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }} + + s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithResource(res)) + Expect(s.fs).NotTo(BeNil()) + Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) + Expect(s.filePerm).To(Equal(defaultFilePermission)) + Expect(s.injector.config).To(BeNil()) + Expect(s.injector.boilerplate).To(Equal("")) + Expect(s.injector.resource).NotTo(BeNil()) + Expect(s.injector.resource.GVK.IsEqualTo(res.GVK)).To(BeTrue()) }) }) Describe("Scaffold.Execute", func() { - const fileContent = "Hello world!" + const ( + path = "filename" + pathGo = path + ".go" + pathYaml = path + ".yaml" + content = "Hello world!" + ) var ( - output bytes.Buffer testErr = errors.New("error text") + + s *Scaffold ) BeforeEach(func() { - output.Reset() + s = &Scaffold{fs: afero.NewMemMapFs()} }) DescribeTable("successes", - func(expected string, files ...file.Builder) { - s := &scaffold{ - fs: filesystem.NewMock( - filesystem.MockOutput(&output), - ), - } - - Expect(s.Execute(model.NewUniverse(), files...)).To(Succeed()) - Expect(output.String()).To(Equal(expected)) + func(path, expected string, files ...Builder) { + Expect(s.Execute(files...)).To(Succeed()) + + b, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(Equal(expected)) }, Entry("should write the file", - fileContent, - fakeTemplate{body: fileContent}, + path, content, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}, body: content}, ), Entry("should skip optional models if already have one", - fileContent, - fakeTemplate{body: fileContent}, - fakeTemplate{}, + path, content, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}, body: content}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, ), Entry("should overwrite required models if already have one", - fileContent, - fakeTemplate{}, - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: fileContent}, + path, content, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: OverwriteFile}, body: content}, ), Entry("should format a go file", - "package file\n", - fakeTemplate{fakeBuilder: fakeBuilder{path: "file.go"}, body: "package file"}, + pathGo, "package file\n", + fakeTemplate{fakeBuilder: fakeBuilder{path: pathGo}, body: "package file"}, ), ) DescribeTable("file builders related errors", - func(f func(error) bool, files ...file.Builder) { - s := &scaffold{fs: filesystem.NewMock()} - - Expect(f(s.Execute(model.NewUniverse(), files...))).To(BeTrue()) + func(errType interface{}, files ...Builder) { + err := s.Execute(files...) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, errType)).To(BeTrue()) }, Entry("should fail if unable to validate a file builder", - file.IsValidateError, + &ValidateError{}, fakeRequiresValidation{validateErr: testErr}, ), Entry("should fail if unable to set default values for a template", - file.IsSetTemplateDefaultsError, + &SetTemplateDefaultsError{}, fakeTemplate{err: testErr}, ), Entry("should fail if an unexpected previous model is found", - IsModelAlreadyExistsError, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename"}}, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}}, + &ModelAlreadyExistsError{}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: Error}}, ), - Entry("should fail if behavior if file exists is not defined", - IsUnknownIfExistsActionError, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename"}}, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: -1}}, + Entry("should fail if behavior if-exists-action is not defined", + &UnknownIfExistsActionError{}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, + fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: -1}}, ), ) // Following errors are unwrapped, so we need to check for substrings DescribeTable("template related errors", - func(errMsg string, files ...file.Builder) { - s := &scaffold{fs: filesystem.NewMock()} - - err := s.Execute(model.NewUniverse(), files...) + func(errMsg string, files ...Builder) { + err := s.Execute(files...) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(errMsg)) }, @@ -181,37 +196,42 @@ var _ = Describe("Scaffold", func() { ), Entry("should fail if unable to format a go file", "expected 'package', found ", - fakeTemplate{fakeBuilder: fakeBuilder{path: "file.go"}, body: fileContent}, + fakeTemplate{fakeBuilder: fakeBuilder{path: pathGo}, body: content}, ), ) DescribeTable("insert strings", - func(input, expected string, files ...file.Builder) { - s := &scaffold{ - fs: filesystem.NewMock( - filesystem.MockInput(bytes.NewBufferString(input)), - filesystem.MockOutput(&output), - filesystem.MockExists(func(_ string) bool { return len(input) != 0 }), - ), - } - - Expect(s.Execute(model.NewUniverse(), files...)).To(Succeed()) - Expect(output.String()).To(Equal(expected)) + func(path, input, expected string, files ...Builder) { + Expect(afero.WriteFile(s.fs, path, []byte(input), 0666)).To(Succeed()) + + Expect(s.Execute(files...)).To(Succeed()) + + b, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(Equal(expected)) }, Entry("should insert lines for go files", - ` + pathGo, + `package test + //+kubebuilder:scaffold:- `, - ` -1 -2 + `package test + +var a int +var b int + //+kubebuilder:scaffold:- `, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathGo}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor(pathGo, "-"): {"var a int\n", "var b int\n"}, + }, }, ), Entry("should insert lines for yaml files", + pathYaml, ` #+kubebuilder:scaffold:- `, @@ -220,293 +240,224 @@ var _ = Describe("Scaffold", func() { 2 #+kubebuilder:scaffold:- `, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.yaml", "-"): {"1\n", "2\n"}}, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + }, }, ), Entry("should use models if there is no file", + pathYaml, "", ` 1 2 -//+kubebuilder:scaffold:- +#+kubebuilder:scaffold:- `, - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: ` -//+kubebuilder:scaffold:- + fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml, ifExistsAction: OverwriteFile}, body: ` +#+kubebuilder:scaffold:- `}, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + }, }, ), Entry("should use required models over files", - fileContent, + pathYaml, + content, ` 1 2 -//+kubebuilder:scaffold:- +#+kubebuilder:scaffold:- `, - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: ` -//+kubebuilder:scaffold:- + fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml, ifExistsAction: OverwriteFile}, body: ` +#+kubebuilder:scaffold:- `}, - fakeInserter{codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}}, + fakeInserter{ + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + }, }, ), Entry("should use files over optional models", + pathYaml, ` -//+kubebuilder:scaffold:- +#+kubebuilder:scaffold:- `, ` 1 2 -//+kubebuilder:scaffold:- +#+kubebuilder:scaffold:- `, - fakeTemplate{body: fileContent}, + fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml}, body: content}, fakeInserter{ - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, }, }, ), Entry("should filter invalid markers", + pathYaml, ` -//+kubebuilder:scaffold:- -//+kubebuilder:scaffold:* +#+kubebuilder:scaffold:- +#+kubebuilder:scaffold:* `, ` 1 2 -//+kubebuilder:scaffold:- -//+kubebuilder:scaffold:* +#+kubebuilder:scaffold:- +#+kubebuilder:scaffold:* `, fakeInserter{ - markers: []file.Marker{file.NewMarkerFor("file.go", "-")}, - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, - file.NewMarkerFor("file.go", "*"): {"3\n", "4\n"}, + fakeBuilder: fakeBuilder{path: pathYaml}, + markers: []Marker{NewMarkerFor(pathYaml, "-")}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + NewMarkerFor(pathYaml, "*"): {"3\n", "4\n"}, }, }, ), Entry("should filter already existing one-line code fragments", + pathYaml, ` 1 -//+kubebuilder:scaffold:- +#+kubebuilder:scaffold:- 3 4 -//+kubebuilder:scaffold:* +#+kubebuilder:scaffold:* `, ` 1 2 -//+kubebuilder:scaffold:- +#+kubebuilder:scaffold:- 3 4 -//+kubebuilder:scaffold:* +#+kubebuilder:scaffold:* `, fakeInserter{ - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {"1\n", "2\n"}, - file.NewMarkerFor("file.go", "*"): {"3\n", "4\n"}, + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, + NewMarkerFor(pathYaml, "*"): {"3\n", "4\n"}, }, }, ), Entry("should not insert anything if no code fragment", - "", // input is provided through a template as mock fs doesn't copy it to the output buffer if no-op + pathYaml, ` -//+kubebuilder:scaffold:- +#+kubebuilder:scaffold:- +`, + ` +#+kubebuilder:scaffold:- `, - fakeTemplate{body: ` -//+kubebuilder:scaffold:- -`}, fakeInserter{ - codeFragments: file.CodeFragmentsMap{ - file.NewMarkerFor("file.go", "-"): {}, + fakeBuilder: fakeBuilder{path: pathYaml}, + codeFragments: CodeFragmentsMap{ + NewMarkerFor(pathYaml, "-"): {}, }, }, ), ) DescribeTable("insert strings related errors", - func(f func(error) bool, files ...file.Builder) { - s := &scaffold{ - fs: filesystem.NewMock( - filesystem.MockExists(func(_ string) bool { return true }), - ), - } - - err := s.Execute(model.NewUniverse(), files...) + func(errType interface{}, files ...Builder) { + Expect(afero.WriteFile(s.fs, path, []byte{}, 0666)).To(Succeed()) + + err := s.Execute(files...) Expect(err).To(HaveOccurred()) - Expect(f(err)).To(BeTrue()) + Expect(errors.As(err, errType)).To(BeTrue()) }, Entry("should fail if inserting into a model that fails when a file exists and it does exist", - IsFileAlreadyExistsError, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}}, + &FileAlreadyExistsError{}, + fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: Error}}, fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, ), Entry("should fail if inserting into a model with unknown behavior if the file exists and it does exist", - IsUnknownIfExistsActionError, + &UnknownIfExistsActionError{}, fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: -1}}, fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, ), ) - It("should fail if a plugin fails", func() { - s := &scaffold{ - fs: filesystem.NewMock(), - plugins: []model.Plugin{fakePlugin{err: testErr}}, - } - - err := s.Execute( - model.NewUniverse(), - fakeTemplate{}, - ) - Expect(err).To(MatchError(testErr)) - Expect(model.IsPluginError(err)).To(BeTrue()) - }) - Context("write when the file already exists", func() { - var s Scaffold - BeforeEach(func() { - s = &scaffold{ - fs: filesystem.NewMock( - filesystem.MockExists(func(_ string) bool { return true }), - filesystem.MockOutput(&output), - ), - } + _ = afero.WriteFile(s.fs, path, []byte{}, 0666) }) It("should skip the file by default", func() { - Expect(s.Execute( - model.NewUniverse(), - fakeTemplate{body: fileContent}, - )).To(Succeed()) - Expect(output.String()).To(BeEmpty()) + Expect(s.Execute(fakeTemplate{ + fakeBuilder: fakeBuilder{path: path}, + body: content, + })).To(Succeed()) + + b, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(BeEmpty()) }) It("should write the file if configured to do so", func() { - Expect(s.Execute( - model.NewUniverse(), - fakeTemplate{fakeBuilder: fakeBuilder{ifExistsAction: file.Overwrite}, body: fileContent}, - )).To(Succeed()) - Expect(output.String()).To(Equal(fileContent)) + Expect(s.Execute(fakeTemplate{ + fakeBuilder: fakeBuilder{path: path, ifExistsAction: OverwriteFile}, + body: content, + })).To(Succeed()) + + b, err := afero.ReadFile(s.fs, path) + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(Equal(content)) }) It("should error if configured to do so", func() { - err := s.Execute( - model.NewUniverse(), - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: file.Error}, body: fileContent}, - ) + err := s.Execute(fakeTemplate{ + fakeBuilder: fakeBuilder{path: path, ifExistsAction: Error}, + body: content, + }) Expect(err).To(HaveOccurred()) - Expect(IsFileAlreadyExistsError(err)).To(BeTrue()) - Expect(output.String()).To(BeEmpty()) + Expect(errors.As(err, &FileAlreadyExistsError{})).To(BeTrue()) }) }) - - DescribeTable("filesystem errors", - func( - mockErrorF func(error) filesystem.MockOptions, - checkErrorF func(error) bool, - files ...file.Builder, - ) { - s := &scaffold{ - fs: filesystem.NewMock( - mockErrorF(testErr), - ), - } - - err := s.Execute(model.NewUniverse(), files...) - Expect(err).To(HaveOccurred()) - Expect(checkErrorF(err)).To(BeTrue()) - }, - Entry("should fail if fs.Exists failed (at file writing)", - filesystem.MockExistsError, filesystem.IsFileExistsError, - fakeTemplate{}, - ), - Entry("should fail if fs.Exists failed (at model updating)", - filesystem.MockExistsError, filesystem.IsFileExistsError, - fakeTemplate{}, - fakeInserter{}, - ), - Entry("should fail if fs.Open was unable to open the file", - filesystem.MockOpenFileError, filesystem.IsOpenFileError, - fakeInserter{}, - ), - Entry("should fail if fs.Open().Read was unable to read the file", - filesystem.MockReadFileError, filesystem.IsReadFileError, - fakeInserter{}, - ), - Entry("should fail if fs.Open().Close was unable to close the file", - filesystem.MockCloseFileError, filesystem.IsCloseFileError, - fakeInserter{}, - ), - Entry("should fail if fs.Create was unable to create the directory", - filesystem.MockCreateDirError, filesystem.IsCreateDirectoryError, - fakeTemplate{}, - ), - Entry("should fail if fs.Create was unable to create the file", - filesystem.MockCreateFileError, filesystem.IsCreateFileError, - fakeTemplate{}, - ), - Entry("should fail if fs.Create().Write was unable to write the file", - filesystem.MockWriteFileError, filesystem.IsWriteFileError, - fakeTemplate{}, - ), - Entry("should fail if fs.Create().Write was unable to close the file", - filesystem.MockCloseFileError, filesystem.IsCloseFileError, - fakeTemplate{}, - ), - ) }) }) -var _ model.Plugin = fakePlugin{} - -// fakePlugin is used to mock a model.Plugin in order to test Scaffold -type fakePlugin struct { - err error -} - -// Pipe implements model.Plugin -func (f fakePlugin) Pipe(_ *model.Universe) error { - return f.err -} - -var _ file.Builder = fakeBuilder{} +var _ Builder = fakeBuilder{} -// fakeBuilder is used to mock a file.Builder +// fakeBuilder is used to mock a Builder type fakeBuilder struct { path string - ifExistsAction file.IfExistsAction + ifExistsAction IfExistsAction } -// GetPath implements file.Builder +// GetPath implements Builder func (f fakeBuilder) GetPath() string { return f.path } -// GetIfExistsAction implements file.Builder -func (f fakeBuilder) GetIfExistsAction() file.IfExistsAction { +// GetIfExistsAction implements Builder +func (f fakeBuilder) GetIfExistsAction() IfExistsAction { return f.ifExistsAction } -var _ file.RequiresValidation = fakeRequiresValidation{} +var _ RequiresValidation = fakeRequiresValidation{} -// fakeRequiresValidation is used to mock a file.RequiresValidation in order to test Scaffold +// fakeRequiresValidation is used to mock a RequiresValidation in order to test Scaffold type fakeRequiresValidation struct { fakeBuilder validateErr error } -// Validate implements file.RequiresValidation +// Validate implements RequiresValidation func (f fakeRequiresValidation) Validate() error { return f.validateErr } -var _ file.Template = fakeTemplate{} +var _ Template = fakeTemplate{} -// fakeTemplate is used to mock a file.File in order to test Scaffold +// fakeTemplate is used to mock a File in order to test Scaffold type fakeTemplate struct { fakeBuilder @@ -514,12 +465,12 @@ type fakeTemplate struct { err error } -// GetBody implements file.Template +// GetBody implements Template func (f fakeTemplate) GetBody() string { return f.body } -// SetTemplateDefaults implements file.Template +// SetTemplateDefaults implements Template func (f fakeTemplate) SetTemplateDefaults() error { if f.err != nil { return f.err @@ -531,24 +482,24 @@ func (f fakeTemplate) SetTemplateDefaults() error { type fakeInserter struct { fakeBuilder - markers []file.Marker - codeFragments file.CodeFragmentsMap + markers []Marker + codeFragments CodeFragmentsMap } -// GetMarkers implements file.UpdatableTemplate -func (f fakeInserter) GetMarkers() []file.Marker { +// GetMarkers implements Inserter +func (f fakeInserter) GetMarkers() []Marker { if f.markers != nil { return f.markers } - markers := make([]file.Marker, 0, len(f.codeFragments)) + markers := make([]Marker, 0, len(f.codeFragments)) for marker := range f.codeFragments { markers = append(markers, marker) } return markers } -// GetCodeFragments implements file.UpdatableTemplate -func (f fakeInserter) GetCodeFragments() file.CodeFragmentsMap { +// GetCodeFragments implements Inserter +func (f fakeInserter) GetCodeFragments() CodeFragmentsMap { return f.codeFragments } diff --git a/pkg/plugins/util/cleanup.go b/pkg/plugins/util/cleanup.go new file mode 100644 index 00000000..bfc4f2e5 --- /dev/null +++ b/pkg/plugins/util/cleanup.go @@ -0,0 +1,137 @@ +// Copyright 2021 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" +) + +// RemoveKustomizeCRDManifests removes items in config/crd relating to CRD conversion webhooks. +func RemoveKustomizeCRDManifests() error { + + pathsToRemove := []string{ + filepath.Join("config", "crd", "kustomizeconfig.yaml"), + } + configPatchesDir := filepath.Join("config", "crd", "patches") + webhookPatchMatches, err := filepath.Glob(filepath.Join(configPatchesDir, "webhook_in_*.yaml")) + if err != nil { + return err + } + pathsToRemove = append(pathsToRemove, webhookPatchMatches...) + cainjectionPatchMatches, err := filepath.Glob(filepath.Join(configPatchesDir, "cainjection_in_*.yaml")) + if err != nil { + return err + } + pathsToRemove = append(pathsToRemove, cainjectionPatchMatches...) + for _, p := range pathsToRemove { + if err := os.RemoveAll(p); err != nil { + return err + } + } + children, err := ioutil.ReadDir(configPatchesDir) + if err == nil && len(children) == 0 { + if err := os.RemoveAll(configPatchesDir); err != nil { + return err + } + } + return nil +} + +// UpdateKustomizationsCreateAPI updates certain parts of or removes entire kustomization.yaml files +// that are either not used by certain CreateAPI plugins or are created by preceding CreateAPI plugins. +func UpdateKustomizationsCreateAPI() error { + + crdKFile := filepath.Join("config", "crd", "kustomization.yaml") + if crdKBytes, err := ioutil.ReadFile(crdKFile); err != nil && !errors.Is(err, os.ErrNotExist) { + log.Debugf("Error reading kustomization for substitution: %v", err) + } else if err == nil { + if bytes.Contains(crdKBytes, []byte("[WEBHOOK]")) || bytes.Contains(crdKBytes, []byte("[CERTMANAGER]")) { + if err := os.RemoveAll(crdKFile); err != nil { + log.Debugf("Error removing file prior to scaffold: %v", err) + } + } + } + + return nil +} + +// UpdateKustomizationsInit updates certain parts of or removes entire kustomization.yaml files +// that are either not used by certain Init plugins or are created by preceding Init plugins. +func UpdateKustomizationsInit() error { + + defaultKFile := filepath.Join("config", "default", "kustomization.yaml") + if err := ReplaceInFile(defaultKFile, + ` +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- ../webhook +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. +#- ../certmanager`, ""); err != nil { + return fmt.Errorf("remove %s resources: %v", defaultKFile, err) + } + + if err := ReplaceInFile(defaultKFile, + ` +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- manager_webhook_patch.yaml + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. +# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. +# 'CERTMANAGER' needs to be enabled to use ca injection +#- webhookcainjection_patch.yaml + +# the following config is for teaching kustomize how to do var substitution +vars: +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. +#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR +# objref: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +# fieldref: +# fieldpath: metadata.namespace +#- name: CERTIFICATE_NAME +# objref: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +#- name: SERVICE_NAMESPACE # namespace of the service +# objref: +# kind: Service +# version: v1 +# name: webhook-service +# fieldref: +# fieldpath: metadata.namespace +#- name: SERVICE_NAME +# objref: +# kind: Service +# version: v1 +# name: webhook-service +`, ""); err != nil { + return fmt.Errorf("remove %s patch and vars blocks: %v", defaultKFile, err) + } + + return nil +} diff --git a/pkg/plugins/util/utils.go b/pkg/plugins/util/utils.go new file mode 100644 index 00000000..0a9aa3aa --- /dev/null +++ b/pkg/plugins/util/utils.go @@ -0,0 +1,81 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// todo(camilamacedo86): push this helpers to kubbuilder + +package util + +import ( + "errors" + "io/ioutil" + "os" + "regexp" + "strings" +) + +func ReplaceInFile(path, old, new string) error { + info, err := os.Stat(path) + if err != nil { + return err + } + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + if !strings.Contains(string(b), old) { + return errors.New("unable to find the content to be replaced") + } + s := strings.Replace(string(b), old, new, -1) + err = ioutil.WriteFile(path, []byte(s), info.Mode()) + if err != nil { + return err + } + return nil +} + +func ReplaceRegexInFile(path, match, replace string) error { + matcher, err := regexp.Compile(match) + if err != nil { + return err + } + info, err := os.Stat(path) + if err != nil { + return err + } + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + s := matcher.ReplaceAllString(string(b), replace) + if s == string(b) { + return errors.New("unable to find the content to be replaced") + } + err = ioutil.WriteFile(path, []byte(s), info.Mode()) + if err != nil { + return err + } + return nil +} + +// InsertCode searches target content in the file and insert `toInsert` after the target. +func InsertCode(filename, target, code string) error { + contents, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + idx := strings.Index(string(contents), target) + out := string(contents[:idx+len(target)]) + code + string(contents[idx+len(target):]) + // false positive + // nolint:gosec + return ioutil.WriteFile(filename, []byte(out), 0644) +} diff --git a/pkg/plugins/v1/api.go b/pkg/plugins/v1/api.go index 0473f7cb..ff35d065 100644 --- a/pkg/plugins/v1/api.go +++ b/pkg/plugins/v1/api.go @@ -15,137 +15,204 @@ package v1 import ( + "errors" "fmt" "strings" + "github.com/iancoleman/strcase" + "github.com/joelanford/helm-operator/pkg/plugins/util" + "github.com/joelanford/helm-operator/pkg/plugins/v1/chartutil" + "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds" "github.com/spf13/pflag" + "helm.sh/helm/v3/pkg/chart" "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + pluginutil "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" +) - "github.com/joelanford/helm-operator/pkg/plugins/internal/kubebuilder/cmdutil" - "github.com/joelanford/helm-operator/pkg/plugins/v1/chartutil" - "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds" +const ( + crdVersionFlag = "crd-version" + helmChartFlag = "helm-chart" + helmChartRepoFlag = "helm-chart-repo" + helmChartVersionFlag = "helm-chart-version" + + defaultCrdVersion = "v1" + + // defaultGroup is the Kubernetes CRD API Group used for fetched charts when the --group flag is not specified + defaultGroup = "charts" + // defaultVersion is the Kubernetes CRD API Version used for fetched charts when the --version flag is not specified + defaultVersion = "v1alpha1" ) -type createAPISubcommand struct { - config config.Config - createOptions chartutil.CreateOptions +type createAPIOptions struct { + // CRDVersion is the version of the `apiextensions.k8s.io` API which will be used to generate the CRD. + CRDVersion string + + chartOptions chartutil.Options } -var ( - _ plugin.CreateAPISubcommand = &createAPISubcommand{} - _ cmdutil.RunOptions = &createAPISubcommand{} -) +// UpdateResource updates the base resource with the information obtained from the flags +func (opts createAPIOptions) UpdateResource(res *resource.Resource) { + res.API = &resource.API{ + CRDVersion: opts.CRDVersion, + Namespaced: true, + } + + // Ensure that Path is empty and Controller false + res.Path = "" + res.Controller = false +} + +var _ plugin.CreateAPISubcommand = &createAPISubcommand{} + +type createAPISubcommand struct { + config config.Config + resource *resource.Resource + chart *chart.Chart + options createAPIOptions +} -// UpdateContext define plugin context -func (p createAPISubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `Scaffold a Kubernetes API that is backed by a Helm chart. +func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Scaffold a Kubernetes API that is backed by a Helm chart. ` - ctx.Examples = fmt.Sprintf(` $ %s create api \ + subcmdMeta.Examples = fmt.Sprintf(` $ %s create api \ --group=apps --version=v1alpha1 \ --kind=AppService - $ %s create api \ + $ %[1]s create api \ --group=apps --version=v1alpha1 \ --kind=AppService \ --helm-chart=myrepo/app - $ %s create api \ + $ %[1]s create api \ --helm-chart=myrepo/app - $ %s create api \ + $ %[1]s create api \ --helm-chart=myrepo/app \ --helm-chart-version=1.2.3 - $ %s create api \ + $ %[1]s create api \ --helm-chart=app \ --helm-chart-repo=https://charts.mycompany.com/ - $ %s create api \ + $ %[1]s create api \ --helm-chart=app \ --helm-chart-repo=https://charts.mycompany.com/ \ --helm-chart-version=1.2.3 - $ %s create api \ + $ %[1]s create api \ --helm-chart=/path/to/local/chart-directories/app/ - $ %s create api \ + $ %[1]s create api \ --helm-chart=/path/to/local/chart-archives/app-1.2.3.tgz -`, - ctx.CommandName, - ctx.CommandName, - ctx.CommandName, - ctx.CommandName, - ctx.CommandName, - ctx.CommandName, - ctx.CommandName, - ctx.CommandName, - ) +`, cliMeta.CommandName) } -const ( - groupFlag = "group" - versionFlag = "version" - kindFlag = "kind" - helmChartFlag = "helm-chart" - helmChartRepoFlag = "helm-chart-repo" - helmChartVersionFlag = "helm-chart-version" - crdVersionFlag = "crd-version" - - crdVersionV1 = "v1" -) - // BindFlags will set the flags for the plugin func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { - p.createOptions = chartutil.CreateOptions{} fs.SortFlags = false - fs.StringVar(&p.createOptions.GVK.Group, groupFlag, "", "resource group") - fs.StringVar(&p.createOptions.GVK.Version, versionFlag, "", "resource version") - fs.StringVar(&p.createOptions.GVK.Kind, kindFlag, "", "resource kind") - - fs.StringVar(&p.createOptions.Chart, helmChartFlag, "", "helm chart") - fs.StringVar(&p.createOptions.Repo, helmChartRepoFlag, "", "helm chart repository") - fs.StringVar(&p.createOptions.Version, helmChartVersionFlag, "", "helm chart version (default: latest)") + fs.StringVar(&p.options.chartOptions.Chart, helmChartFlag, "", "helm chart") + fs.StringVar(&p.options.chartOptions.Repo, helmChartRepoFlag, "", "helm chart repository") + fs.StringVar(&p.options.chartOptions.Version, helmChartVersionFlag, "", "helm chart version (default: latest)") - fs.StringVar(&p.createOptions.CRDVersion, crdVersionFlag, crdVersionV1, "crd version to generate") + fs.StringVar(&p.options.CRDVersion, crdVersionFlag, defaultCrdVersion, "crd version to generate") } -// InjectConfig will inject the PROJECT file/config in the plugin -func (p *createAPISubcommand) InjectConfig(c config.Config) { +func (p *createAPISubcommand) InjectConfig(c config.Config) error { p.config = c -} -// Run will call the plugin actions according to the definitions done in RunOptions interface -func (p *createAPISubcommand) Run() error { - return cmdutil.Run(p) + return nil } -// Validate perform the required validations for this plugin -func (p *createAPISubcommand) Validate() error { - if len(strings.TrimSpace(p.createOptions.Chart)) == 0 { - if len(strings.TrimSpace(p.createOptions.Repo)) != 0 { +func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + + // The following checks and the chart creation would be a better fit for PreScaffold method + // but, as having a chart sets some default values for the resource's GVK, we need to do it here. + var err error + if len(strings.TrimSpace(p.options.chartOptions.Chart)) == 0 { + // Chart repo and version can only be provided if chart was provided. + if len(strings.TrimSpace(p.options.chartOptions.Repo)) != 0 { return fmt.Errorf("value of --%s can only be used with --%s", helmChartRepoFlag, helmChartFlag) - } else if len(p.createOptions.Version) != 0 { + } + if len(p.options.chartOptions.Version) != 0 { return fmt.Errorf("value of --%s can only be used with --%s", helmChartVersionFlag, helmChartFlag) } - r := p.createOptions.Resource() - r.Domain = p.config.GetDomain() - if err := r.Validate(); err != nil { + // Kind is required if no chart was provided as it is used for the chart name. + // While the resource validation will detect this, the error yielded would not + // mention the option of providing the chart flag. Additionally, by checking it + // here we can create the new chart before resource validation. + if len(p.resource.Kind) == 0 { + return fmt.Errorf("either --%s or --%s need to be provided", kindFlag, helmChartFlag) + } + + p.chart, err = chartutil.NewChart(strings.ToLower(p.resource.Kind)) + if err != nil { + return err + } + } else { + p.chart, err = chartutil.LoadChart(p.options.chartOptions) + if err != nil { return err } + + // In case we loaded a chart and some resource flags were not set we will set defaults. + if p.resource.Group == "" { + p.resource.Group = defaultGroup + } + if p.resource.Version == "" { + p.resource.Version = defaultVersion + } + if p.resource.Kind == "" { + p.resource.Kind = strcase.ToCamel(p.chart.Name()) + if p.resource.Plural == "" { + p.resource.Plural = resource.RegularPlural(p.resource.Kind) + } + } + } + + p.options.UpdateResource(p.resource) + + if err := p.resource.Validate(); err != nil { + return err + } + + // Check that resource doesn't have the API scaffolded + if res, err := p.config.GetResource(p.resource.GVK); err == nil && res.HasAPI() { + return errors.New("the API resource already exists") + } + + // Check that the provided group can be added to the project + if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) { + return fmt.Errorf("multiple groups are not allowed by default, to enable multi-group set 'multigroup: true' in your PROJECT file") + } + + // Selected CRD version must match existing CRD versions. + if pluginutil.HasDifferentCRDVersion(p.config, p.resource.API.CRDVersion) { + return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q", p.resource.API.CRDVersion) } return nil } -// GetScaffolder returns cmdutil.Scaffolder which will be executed due the RunOptions interface implementation -func (p *createAPISubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - return scaffolds.NewAPIScaffolder(p.config, p.createOptions), nil -} +func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { + if err := util.RemoveKustomizeCRDManifests(); err != nil { + return fmt.Errorf("error removing kustomization CRD manifests: %v", err) + } + if err := util.UpdateKustomizationsCreateAPI(); err != nil { + return fmt.Errorf("error updating kustomization.yaml files: %v", err) + } + + scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.chart) + scaffolder.InjectFS(fs) + if err := scaffolder.Scaffold(); err != nil { + return err + } + // NOTE: previous step fetches the dependencies of the chart.Chart, so reloading may be needed if used afterwards -// PostScaffold runs all actions that should be executed after the default plugin scaffold -func (p *createAPISubcommand) PostScaffold() error { return nil } diff --git a/pkg/plugins/v1/chartutil/chart.go b/pkg/plugins/v1/chartutil/chart.go index 835e1add..0e7d88ed 100644 --- a/pkg/plugins/v1/chartutil/chart.go +++ b/pkg/plugins/v1/chartutil/chart.go @@ -20,9 +20,7 @@ import ( "io/ioutil" "os" "path/filepath" - "strings" - "github.com/iancoleman/strcase" log "github.com/sirupsen/logrus" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" @@ -31,30 +29,16 @@ import ( "helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/repo" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) const ( - - // HelmChartsDir is the relative directory within an SDK project where Helm - // charts are stored. - HelmChartsDir string = "helm-charts" - - // DefaultGroup is the Kubernetes CRD API Group used for fetched - // charts when the --group flag is not specified - DefaultGroup string = "charts" - - // DefaultVersion is the Kubernetes CRD API Version used for fetched - // charts when the --version flag is not specified - DefaultVersion string = "v1alpha1" + // HelmChartsDir is the relative directory within a SDK project where Helm charts are stored. + HelmChartsDir = "helm-charts" ) -// CreateOptions is used to configure how a Helm chart is scaffolded +// Options is used to configure how a Helm chart is scaffolded // for a new Helm operator project. -type CreateOptions struct { - GVK schema.GroupVersionKind - +type Options struct { // Chart is a chart reference for a local or remote chart. Chart string @@ -63,51 +47,41 @@ type CreateOptions struct { // Version is the version of the chart to fetch. Version string - - // CRDVersion is the version of the `apiextensions.k8s.io` API which will be used to generate the CRD. - CRDVersion string } -func (opts CreateOptions) Resource() resource.Resource { - return resource.Resource{ - API: &resource.API{ - Namespaced: true, - CRDVersion: opts.CRDVersion, - }, - GVK: resource.GVK{ - Group: opts.GVK.Group, - Version: opts.GVK.Version, - Kind: opts.GVK.Kind, - }, - Plural: resource.RegularPlural(opts.GVK.Kind), +// NewChart creates a new helm chart for the project from helm's default template. +// It returns a chart.Chart that references the newly created chart or an error. +func NewChart(name string) (*chart.Chart, error) { + tmpDir, err := ioutil.TempDir("", "osdk-helm-chart") + if err != nil { + return nil, err } + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + log.Errorf("Failed to remove temporary directory %s: %v", tmpDir, err) + } + }() + + // Create a new chart + chartPath, err := chartutil.Create(name, tmpDir) + if err != nil { + return nil, err + } + + return loader.Load(chartPath) } -// CreateChart scaffolds a new helm chart for the project rooted in projectDir -// based on the passed opts. -// -// It returns a scaffold.Resource that can be used by the caller to create -// other related files. opts.ResourceAPIVersion and opts.ResourceKind are -// used to create the resource and must be specified if opts.Chart is empty. -// -// If opts.Chart is not empty, opts.ResourceAPIVersion and opts.Kind can be -// left unset: opts.ResourceAPIVersion defaults to "charts.helm.k8s.io/v1alpha1" -// and opts.ResourceKind is deduced from the specified opts.Chart. -// -// CreateChart also returns a chart.Chart that references the newly created -// chart. -// -// If opts.Chart is empty, CreateChart scaffolds the default chart from helm's -// default template. +// LoadChart creates a new helm chart for the project based on the passed opts. +// It returns a chart.Chart that references the newly created chart or an error. // -// If opts.Chart is a local file, CreateChart verifies that it is a valid helm -// chart archive and unpacks it into the project's helm charts directory. +// If opts.Chart is a local file, it verifies that it is a valid helm chart +// archive and returns its chart.Chart representation. // -// If opts.Chart is a local directory, CreateChart verifies that it is a valid -// helm chart directory and copies it into the project's helm charts directory. +// If opts.Chart is a local directory, it verifies that it is a valid helm chart +// directory and returns its chart.Chart representation. // -// For any other value of opts.Chart, CreateChart attempts to fetch the helm chart -// from a remote repository. +// For any other value of opts.Chart, it attempts to fetch the helm chart from a +// remote repository. // // If opts.Repo is not specified, the following chart reference formats are supported: // @@ -122,113 +96,35 @@ func (opts CreateOptions) Resource() resource.Resource { // - : Fetch the helm chart named chartName in the helm chart repository // specified by opts.Repo // -// If opts.Version is not set, CreateChart will fetch the latest available version of -// the helm chart. Otherwise, CreateChart will fetch the specified version. +// If opts.Version is not set, it will fetch the latest available version of the helm +// chart. Otherwise, it will fetch the specified version. // opts.Version is not used when opts.Chart itself refers to a specific version, for // example when it is a local path or a URL. -// -// CreateChart returns an error if an error occurs creating the scaffold.Resource or -// creating the chart. -func CreateChart(projectDir string, opts CreateOptions) (*resource.Resource, *chart.Chart, error) { - chartsDir := filepath.Join(projectDir, HelmChartsDir) - err := os.MkdirAll(chartsDir, 0755) +func LoadChart(opts Options) (*chart.Chart, error) { + tmpDir, err := ioutil.TempDir("", "osdk-helm-chart") if err != nil { - return nil, nil, fmt.Errorf("failed to create helm-charts directory: %v", err) + return nil, err } + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + log.Errorf("Failed to remove temporary directory %s: %v", tmpDir, err) + } + }() - var ( - r *resource.Resource - c *chart.Chart - ) + chartPath := opts.Chart - // If we don't have a helm chart reference, scaffold the default chart - // from Helm's default template. Otherwise, fetch it. - if len(opts.Chart) == 0 { - r, c, err = scaffoldChart(chartsDir, opts) - if err != nil { - return nil, nil, fmt.Errorf("failed to scaffold default chart: %v", err) - } - } else { - r, c, err = fetchChart(chartsDir, opts) + // If it is a remote chart, download it to a temp dir first + if _, err := os.Stat(opts.Chart); err != nil { + chartPath, err = downloadChart(tmpDir, opts) if err != nil { - return nil, nil, fmt.Errorf("failed to fetch chart: %v", err) + return nil, err } } - relChartPath := filepath.Join(HelmChartsDir, c.Name()) - absChartPath := filepath.Join(projectDir, relChartPath) - if err := fetchChartDependencies(absChartPath); err != nil { - return nil, nil, fmt.Errorf("failed to fetch chart dependencies: %v", err) - } - - // Reload chart in case dependencies changed - c, err = loader.Load(absChartPath) - if err != nil { - return nil, nil, fmt.Errorf("failed to load chart: %v", err) - } - - fmt.Printf("Created %s\n", relChartPath) - return r, c, nil -} - -func scaffoldChart(destDir string, opts CreateOptions) (*resource.Resource, *chart.Chart, error) { - r := opts.Resource() - chartPath, err := chartutil.Create(strings.ToLower(r.Kind), destDir) - if err != nil { - return nil, nil, err - } - - chart, err := loader.Load(chartPath) - if err != nil { - return nil, nil, err - } - return &r, chart, nil -} - -func fetchChart(destDir string, opts CreateOptions) (*resource.Resource, *chart.Chart, error) { - var ( - chart *chart.Chart - err error - ) - - if _, err = os.Stat(opts.Chart); err == nil { - chart, err = createChartFromDisk(destDir, opts.Chart) - } else { - chart, err = createChartFromRemote(destDir, opts) - } - if err != nil { - return nil, nil, err - } - - chartName := chart.Name() - if len(opts.GVK.Group) == 0 { - opts.GVK.Group = DefaultGroup - } - if len(opts.GVK.Version) == 0 { - opts.GVK.Version = DefaultVersion - } - if len(opts.GVK.Kind) == 0 { - opts.GVK.Kind = strcase.ToCamel(chartName) - } - - r := opts.Resource() - return &r, chart, nil -} - -func createChartFromDisk(destDir, source string) (*chart.Chart, error) { - chart, err := loader.Load(source) - if err != nil { - return nil, err - } - - // Save it into our project's helm-charts directory. - if err := chartutil.SaveDir(chart, destDir); err != nil { - return nil, err - } - return chart, nil + return loader.Load(chartPath) } -func createChartFromRemote(destDir string, opts CreateOptions) (*chart.Chart, error) { +func downloadChart(destDir string, opts Options) (string, error) { settings := cli.New() getters := getter.All(settings) c := downloader.ChartDownloader{ @@ -241,27 +137,46 @@ func createChartFromRemote(destDir string, opts CreateOptions) (*chart.Chart, er if opts.Repo != "" { chartURL, err := repo.FindChartInRepoURL(opts.Repo, opts.Chart, opts.Version, "", "", "", getters) if err != nil { - return nil, err + return "", err } opts.Chart = chartURL } - tmpDir, err := ioutil.TempDir("", "osdk-helm-chart") + chartArchive, _, err := c.DownloadTo(opts.Chart, opts.Version, destDir) if err != nil { - return nil, err + return "", err } - defer func() { - if err := os.RemoveAll(tmpDir); err != nil { - log.Errorf("Failed to remove temporary directory %s: %s", tmpDir, err) - } - }() - chartArchive, _, err := c.DownloadTo(opts.Chart, opts.Version, tmpDir) + return chartArchive, nil +} + +// ScaffoldChart scaffolds the provided chart.Chart to a known directory relative to projectDir +// +// It also fetches the dependencies and reloads the chart.Chart +// +// It returns the reloaded chart, the relative path, or an error. +func ScaffoldChart(chrt *chart.Chart, projectDir string) (*chart.Chart, string, error) { + chartsPath := filepath.Join(projectDir, HelmChartsDir) + + // Save it into our project's helm-charts directory. + if err := chartutil.SaveDir(chrt, chartsPath); err != nil { + return chrt, "", err + } + + chartPath := filepath.Join(chartsPath, chrt.Name()) + + // Fetch dependencies + if err := fetchChartDependencies(chartPath); err != nil { + return chrt, "", fmt.Errorf("failed to fetch chart dependencies: %w", err) + } + + // Reload chart in case dependencies changed + chrt, err := loader.Load(chartPath) if err != nil { - return nil, err + return chrt, "", fmt.Errorf("failed to reload chart: %w", err) } - return createChartFromDisk(destDir, chartArchive) + return chrt, filepath.Join(HelmChartsDir, chrt.Name()), nil } func fetchChartDependencies(chartPath string) error { diff --git a/pkg/plugins/v1/chartutil/chart_test.go b/pkg/plugins/v1/chartutil/chart_test.go index 8a64879b..35af56db 100644 --- a/pkg/plugins/v1/chartutil/chart_test.go +++ b/pkg/plugins/v1/chartutil/chart_test.go @@ -18,18 +18,17 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/assert" - "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/repo/repotest" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "github.com/joelanford/helm-operator/pkg/plugins/v1/chartutil" ) -func TestCreateChart(t *testing.T) { +func TestChart(t *testing.T) { srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz") if err != nil { t.Fatalf("Failed to create new temp server: %s", err) @@ -45,11 +44,8 @@ func TestCreateChart(t *testing.T) { latestVersion = "1.2.3" previousVersion = "1.2.0" nonExistentVersion = "0.0.1" - customGroup = "example.com" - customVersion = "v1" customKind = "MyApp" customExpectName = "myapp" - expectDerivedKind = "TestChart" ) testCases := []createChartTestCase{ @@ -57,10 +53,6 @@ func TestCreateChart(t *testing.T) { name: "from scaffold no apiVersion", expectErr: true, }, - { - name: "from scaffold no kind", - expectErr: true, - }, { name: "version without helm chart", helmChartVersion: latestVersion, @@ -78,54 +70,32 @@ func TestCreateChart(t *testing.T) { expectErr: true, }, { - name: "from scaffold with apiVersion and kind", - group: customGroup, - version: customVersion, + name: "from scaffold with kind", kind: customKind, - crdVersion: "v1beta1", - expectResource: mustNewResource(customGroup, customVersion, customKind, "v1beta1"), expectChartName: customExpectName, expectChartVersion: "0.1.0", }, { name: "from directory", helmChart: filepath.Join(".", "testdata", chartName), - crdVersion: "v1beta1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, expectDerivedKind, "v1beta1"), expectChartName: chartName, expectChartVersion: latestVersion, }, { name: "from archive", helmChart: filepath.Join(".", "testdata", fmt.Sprintf("%s-%s.tgz", chartName, latestVersion)), - crdVersion: "v1beta1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, expectDerivedKind, "v1beta1"), expectChartName: chartName, expectChartVersion: latestVersion, }, { name: "from url", helmChart: fmt.Sprintf("%s/%s-%s.tgz", srv.URL(), chartName, latestVersion), - crdVersion: "v1beta1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, expectDerivedKind, "v1beta1"), expectChartName: chartName, expectChartVersion: latestVersion, }, { name: "from repo and name implicit latest", helmChart: "test/" + chartName, - crdVersion: "v1beta1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, expectDerivedKind, "v1beta1"), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from repo and name implicit latest with apiVersion", - helmChart: "test/" + chartName, - group: customGroup, - version: customVersion, - crdVersion: "v1beta1", - expectResource: mustNewResource(customGroup, customVersion, expectDerivedKind, "v1beta1"), expectChartName: chartName, expectChartVersion: latestVersion, }, @@ -133,19 +103,6 @@ func TestCreateChart(t *testing.T) { name: "from repo and name implicit latest with kind", helmChart: "test/" + chartName, kind: customKind, - crdVersion: "v1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, customKind, "v1"), - expectChartName: chartName, - expectChartVersion: latestVersion, - }, - { - name: "from repo and name implicit latest with apiVersion and kind", - helmChart: "test/" + chartName, - group: customGroup, - version: customVersion, - kind: customKind, - crdVersion: "v1", - expectResource: mustNewResource(customGroup, customVersion, customKind, "v1"), expectChartName: chartName, expectChartVersion: latestVersion, }, @@ -153,8 +110,6 @@ func TestCreateChart(t *testing.T) { name: "from repo and name explicit latest", helmChart: "test/" + chartName, helmChartVersion: latestVersion, - crdVersion: "v1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, expectDerivedKind, "v1"), expectChartName: chartName, expectChartVersion: latestVersion, }, @@ -162,8 +117,6 @@ func TestCreateChart(t *testing.T) { name: "from repo and name explicit previous", helmChart: "test/" + chartName, helmChartVersion: previousVersion, - crdVersion: "v1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, expectDerivedKind, "v1"), expectChartName: chartName, expectChartVersion: previousVersion, }, @@ -171,8 +124,6 @@ func TestCreateChart(t *testing.T) { name: "from name and repo url implicit latest", helmChart: chartName, helmChartRepo: srv.URL(), - crdVersion: "v1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, expectDerivedKind, "v1"), expectChartName: chartName, expectChartVersion: latestVersion, }, @@ -181,8 +132,6 @@ func TestCreateChart(t *testing.T) { helmChart: chartName, helmChartRepo: srv.URL(), helmChartVersion: latestVersion, - crdVersion: "v1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, expectDerivedKind, "v1"), expectChartName: chartName, expectChartVersion: latestVersion, }, @@ -191,8 +140,6 @@ func TestCreateChart(t *testing.T) { helmChart: chartName, helmChartRepo: srv.URL(), helmChartVersion: previousVersion, - crdVersion: "v1", - expectResource: mustNewResource(chartutil.DefaultGroup, chartutil.DefaultVersion, expectDerivedKind, "v1"), expectChartName: chartName, expectChartVersion: previousVersion, }, @@ -208,36 +155,16 @@ func TestCreateChart(t *testing.T) { type createChartTestCase struct { name string - group string - version string kind string - crdVersion string helmChart string helmChartVersion string helmChartRepo string - expectResource *resource.Resource expectChartName string expectChartVersion string expectErr bool } -func mustNewResource(group, version, kind string, crdVersion string) *resource.Resource { - r := &resource.Resource{ - API: &resource.API{ - Namespaced: true, - CRDVersion: crdVersion, - }, - GVK: resource.GVK{ - Group: group, - Version: version, - Kind: kind, - }, - Plural: resource.RegularPlural(kind), - } - return r -} - func runTestCase(t *testing.T, testDir string, tc createChartTestCase) { outputDir := filepath.Join(testDir, "output") assert.NoError(t, os.Mkdir(outputDir, 0755)) @@ -252,18 +179,21 @@ func runTestCase(t *testing.T, testDir string, tc createChartTestCase) { defer os.Unsetenv("HELM_REPOSITORY_CONFIG") defer os.Unsetenv("HELM_REPOSITORY_CACHE") - opts := chartutil.CreateOptions{ - GVK: schema.GroupVersionKind{ - Group: tc.group, - Version: tc.version, - Kind: tc.kind, - }, - CRDVersion: tc.crdVersion, - Chart: tc.helmChart, - Version: tc.helmChartVersion, - Repo: tc.helmChartRepo, + var ( + chrt *chart.Chart + err error + ) + if tc.helmChart != "" { + opts := chartutil.Options{ + Chart: tc.helmChart, + Version: tc.helmChartVersion, + Repo: tc.helmChartRepo, + } + chrt, err = chartutil.LoadChart(opts) + } else { + chrt, err = chartutil.NewChart(strings.ToLower(tc.kind)) } - resource, chrt, err := chartutil.CreateChart(outputDir, opts) + if tc.expectErr { assert.Error(t, err) return @@ -272,14 +202,10 @@ func runTestCase(t *testing.T, testDir string, tc createChartTestCase) { if !assert.NoError(t, err) { return } - assert.Equal(t, tc.expectResource, resource) assert.Equal(t, tc.expectChartName, chrt.Name()) assert.Equal(t, tc.expectChartVersion, chrt.Metadata.Version) - loadedChart, err := loader.Load(filepath.Join(outputDir, chartutil.HelmChartsDir, chrt.Name())) - if err != nil { - t.Fatalf("Could not load chart from expected location: %s", err) - } - - assert.Equal(t, loadedChart, chrt) + _, chartPath, err := chartutil.ScaffoldChart(chrt, outputDir) + assert.NoError(t, err) + assert.Equal(t, filepath.Join(chartutil.HelmChartsDir, tc.expectChartName), chartPath) } diff --git a/pkg/plugins/v1/init.go b/pkg/plugins/v1/init.go index 6e1d91d7..652b111f 100644 --- a/pkg/plugins/v1/init.go +++ b/pkg/plugins/v1/init.go @@ -16,42 +16,41 @@ package v1 import ( "fmt" - "os" "path/filepath" - "strings" + "github.com/joelanford/helm-operator/pkg/plugins/util" + "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds" "github.com/spf13/pflag" - "k8s.io/apimachinery/pkg/util/validation" "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" +) - "github.com/joelanford/helm-operator/pkg/plugins/internal/kubebuilder/cmdutil" - "github.com/joelanford/helm-operator/pkg/plugins/v1/chartutil" - "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds" +const ( + groupFlag = "group" + versionFlag = "version" + kindFlag = "kind" ) type initSubcommand struct { - config config.Config - apiPlugin createAPISubcommand + apiSubcommand createAPISubcommand - domain string - projectName string - - // If true, run the `create api` plugin. - doCreateAPI bool + config config.Config // For help text. commandName string + + // Flags + group string + version string + kind string } -var ( - _ plugin.InitSubcommand = &initSubcommand{} - _ cmdutil.RunOptions = &initSubcommand{} -) +var _ plugin.InitSubcommand = &initSubcommand{} // UpdateContext define plugin context -func (p *initSubcommand) UpdateContext(ctx *plugin.Context) { - ctx.Description = `Initialize a new Helm-based operator project. +func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Initialize a new Helm-based operator project. Writes the following files: - a helm-charts directory with the chart(s) to build releases from @@ -62,7 +61,7 @@ Writes the following files: - a Patch file for customizing image for manager manifests - a Patch file for enabling prometheus metrics ` - ctx.Examples = fmt.Sprintf(` $ %[1]s init --plugins=%[2]s \ + subcmdMeta.Examples = fmt.Sprintf(` $ %[1]s init --plugins=%[2]s \ --domain=example.com \ --group=apps \ --version=v1alpha1 \ @@ -109,90 +108,85 @@ Writes the following files: $ %[1]s init --plugins=%[2]s \ --domain=example.com \ --helm-chart=/path/to/local/chart-archives/app-1.2.3.tgz -`, - ctx.CommandName, pluginKey, - ) +`, cliMeta.CommandName, pluginKey) - p.commandName = ctx.CommandName + p.commandName = cliMeta.CommandName } -// BindFlags will set the flags for the plugin func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { fs.SortFlags = false - fs.StringVar(&p.domain, "domain", "my.domain", "domain for groups") - fs.StringVar(&p.projectName, "project-name", "", "name of this project, the default being directory name") - p.apiPlugin.BindFlags(fs) + fs.StringVar(&p.group, groupFlag, "", "resource Group") + fs.StringVar(&p.version, versionFlag, "", "resource Version") + fs.StringVar(&p.kind, kindFlag, "", "resource Kind") + p.apiSubcommand.BindFlags(fs) } -// InjectConfig will inject the PROJECT file/config in the plugin -func (p *initSubcommand) InjectConfig(c config.Config) { - // v3 project configs get a 'layout' value. - _ = c.SetLayout(pluginKey) +func (p *initSubcommand) InjectConfig(c config.Config) error { p.config = c - p.apiPlugin.config = p.config + return nil } -// Run will call the plugin actions -func (p *initSubcommand) Run() error { - return cmdutil.Run(p) +func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { + if err := addInitCustomizations(p.config.GetProjectName()); err != nil { + return fmt.Errorf("error updating init manifests: %s", err) + } + + scaffolder := scaffolds.NewInitScaffolder(p.config) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() } -// Validate perform the required validations for this plugin -func (p *initSubcommand) Validate() error { - // Set values in the config - if err := p.config.SetProjectName(p.projectName); err != nil { +// addInitCustomizations will perform the required customizations for this plugin on the common base +func addInitCustomizations(projectName string) error { + managerFile := filepath.Join("config", "manager", "manager.yaml") + + // todo: we ought to use afero instead. Replace this methods to insert/update + // by https://github.com/kubernetes-sigs/kubebuilder/pull/2119 + + // Add leader election arg in config/manager/manager.yaml and in config/default/manager_auth_proxy_patch.yaml + err := util.InsertCode(managerFile, + "--leader-elect", + fmt.Sprintf("\n - --leader-election-id=%s", projectName)) + if err != nil { return err } - if err := p.config.SetDomain(p.domain); err != nil { + err = util.InsertCode(filepath.Join("config", "default", "manager_auth_proxy_patch.yaml"), + "- \"--leader-elect\"", + fmt.Sprintf("\n - \"--leader-election-id=%s\"", projectName)) + if err != nil { return err } - // Check if the project name is a valid k8s namespace (DNS 1123 label). - if p.config.GetProjectName() == "" { - dir, err := os.Getwd() - if err != nil { - return fmt.Errorf("error getting current directory: %v", err) - } - if err = p.config.SetProjectName(strings.ToLower(filepath.Base(dir))); err != nil { - return err - } + // Increase the default memory required. + err = util.ReplaceInFile(managerFile, "memory: 30Mi", "memory: 90Mi") + if err != nil { + return err } - - if err := validation.IsDNS1123Label(p.config.GetProjectName()); err != nil { - return fmt.Errorf("project name (%s) is invalid: %v", p.config.GetProjectName(), err) + err = util.ReplaceInFile(managerFile, "memory: 20Mi", "memory: 60Mi") + if err != nil { + return err } - defaultOpts := chartutil.CreateOptions{CRDVersion: "v1"} - if !p.apiPlugin.createOptions.GVK.Empty() || p.apiPlugin.createOptions != defaultOpts { - p.doCreateAPI = true - return p.apiPlugin.Validate() + // Remove the webhook option for the componentConfig since webhooks are not supported by helm + err = util.ReplaceInFile(filepath.Join("config", "manager", "controller_manager_config.yaml"), + "webhook:\n port: 9443", "") + if err != nil { + return err } - return nil -} - -// GetScaffolder returns cmdutil.Scaffolder which will be executed due the RunOptions interface implementation -func (p *initSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { - var ( - apiScaffolder cmdutil.Scaffolder - err error - ) - if p.doCreateAPI { - apiScaffolder, err = p.apiPlugin.GetScaffolder() - if err != nil { - return nil, err - } + // Remove the call to the command as manager. Helm has not been exposing this entrypoint + // todo: provide the manager entrypoint for helm and then remove it + const command = `command: + - /manager + ` + err = util.ReplaceInFile(managerFile, command, "") + if err != nil { + return err } - return scaffolds.NewInitScaffolder(p.config, apiScaffolder), nil -} - -// PostScaffold will run the required actions after the default plugin scaffold -func (p *initSubcommand) PostScaffold() error { - if p.doCreateAPI { - return p.apiPlugin.PostScaffold() + if err := util.UpdateKustomizationsInit(); err != nil { + return fmt.Errorf("error updating kustomization.yaml files: %v", err) } - fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName) return nil } diff --git a/pkg/plugins/v1/scaffolds/api.go b/pkg/plugins/v1/scaffolds/api.go index 18efcf83..cf0259ce 100644 --- a/pkg/plugins/v1/scaffolds/api.go +++ b/pkg/plugins/v1/scaffolds/api.go @@ -18,89 +18,84 @@ limitations under the License. package scaffolds import ( - "errors" "fmt" "os" - "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" - - "github.com/joelanford/helm-operator/pkg/plugins/internal/kubebuilder/cmdutil" - "github.com/joelanford/helm-operator/pkg/plugins/internal/kubebuilder/machinery" "github.com/joelanford/helm-operator/pkg/plugins/v1/chartutil" "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds/internal/templates" "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds/internal/templates/config/crd" "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds/internal/templates/config/rbac" "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds/internal/templates/config/samples" + "helm.sh/helm/v3/pkg/chart" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" ) -var _ cmdutil.Scaffolder = &apiScaffolder{} +var _ plugins.Scaffolder = &apiScaffolder{} // apiScaffolder contains configuration for generating scaffolding for Go type // representing the API and controller that implements the behavior for the API. type apiScaffolder struct { - config config.Config - opts chartutil.CreateOptions + fs machinery.Filesystem + + config config.Config + resource resource.Resource + chrt *chart.Chart } -// NewAPIScaffolder returns a new Scaffolder for API/controller creation operations -func NewAPIScaffolder(config config.Config, opts chartutil.CreateOptions) cmdutil.Scaffolder { +// NewAPIScaffolder returns a new plugins.Scaffolder for API/controller creation operations +func NewAPIScaffolder(cfg config.Config, res resource.Resource, chrt *chart.Chart) plugins.Scaffolder { return &apiScaffolder{ - config: config, - opts: opts, + config: cfg, + resource: res, + chrt: chrt, } } -// Scaffold implements Scaffolder -func (s *apiScaffolder) Scaffold() error { - return s.scaffold() +// InjectFS implements plugins.Scaffolder +func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs } -func (s *apiScaffolder) newUniverse(r *resource.Resource) *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - model.WithResource(r), - ) -} +// Scaffold implements plugins.Scaffolder +func (s *apiScaffolder) Scaffold() error { + if err := s.config.UpdateResource(s.resource); err != nil { + return err + } -func (s *apiScaffolder) scaffold() error { + // Get current directory projectDir, err := os.Getwd() if err != nil { return err } - r, chrt, err := chartutil.CreateChart(projectDir, s.opts) + + // Save the loaded chart.Chart + var chartPath string + s.chrt, chartPath, err = chartutil.ScaffoldChart(s.chrt, projectDir) if err != nil { return err } - r.Domain = s.config.GetDomain() - - // Check that resource doesn't exist - if s.config.HasResource(r.GVK) { - return errors.New("the API resource already exists") - } - // Check that the provided group can be added to the project - if !s.config.IsMultiGroup() && s.config.ResourcesLength() != 0 && !s.config.HasGroup(r.Group) { - return errors.New("multiple groups are not allowed by default, to enable multi-group set 'multigroup: true' in your PROJECT file") - } - - if err := s.config.UpdateResource(*r); err != nil { - return fmt.Errorf("error updating resource in PROJECT file: %v", err) - } + fmt.Printf("Created %s\n", chartPath) + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + // NOTE: kubebuilder's default permissions are only for root users + machinery.WithDirectoryPermissions(0755), + machinery.WithFilePermissions(0644), + machinery.WithConfig(s.config), + machinery.WithResource(&s.resource), + ) - chartPath := filepath.Join(chartutil.HelmChartsDir, chrt.Metadata.Name) - if err := machinery.NewScaffold().Execute( - s.newUniverse(r), + if err := scaffold.Execute( &templates.WatchesUpdater{ChartPath: chartPath}, - &crd.CRD{CRDVersion: s.opts.CRDVersion}, + &crd.CRD{}, &crd.Kustomization{}, - &rbac.CRDEditorRole{}, - &rbac.CRDViewerRole{}, - &rbac.ManagerRoleUpdater{Chart: chrt}, - &samples.CRDSample{ChartPath: chartPath, Chart: chrt}, + &rbac.ManagerRoleUpdater{Chart: s.chrt}, + &samples.CustomResource{ChartPath: chartPath, Chart: s.chrt}, ); err != nil { - return fmt.Errorf("error scaffolding APIs: %v", err) + return fmt.Errorf("error scaffolding APIs: %w", err) } return nil diff --git a/pkg/plugins/v1/scaffolds/init.go b/pkg/plugins/v1/scaffolds/init.go index 821236fc..cbd57470 100644 --- a/pkg/plugins/v1/scaffolds/init.go +++ b/pkg/plugins/v1/scaffolds/init.go @@ -21,17 +21,13 @@ import ( "os" "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" "github.com/joelanford/helm-operator/internal/version" - "github.com/joelanford/helm-operator/pkg/plugins/internal/kubebuilder/cmdutil" - "github.com/joelanford/helm-operator/pkg/plugins/internal/kubebuilder/machinery" "github.com/joelanford/helm-operator/pkg/plugins/v1/chartutil" "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds/internal/templates" - "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds/internal/templates/config/kdefault" - "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds/internal/templates/config/manager" - "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds/internal/templates/config/prometheus" "github.com/joelanford/helm-operator/pkg/plugins/v1/scaffolds/internal/templates/config/rbac" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) const ( @@ -43,44 +39,40 @@ const ( // helmOperatorVersion is set to the version of helm-operator at compile-time. var helmOperatorVersion = mustGetScaffoldVersion() -var _ cmdutil.Scaffolder = &initScaffolder{} +var _ plugins.Scaffolder = &initScaffolder{} type initScaffolder struct { - config config.Config - apiScaffolder cmdutil.Scaffolder + fs machinery.Filesystem + + config config.Config } -// NewInitScaffolder returns a new Scaffolder for project initialization operations -func NewInitScaffolder(config config.Config, apiScaffolder cmdutil.Scaffolder) cmdutil.Scaffolder { +// NewInitScaffolder returns a new plugins.Scaffolder for project initialization operations +func NewInitScaffolder(config config.Config) plugins.Scaffolder { return &initScaffolder{ - config: config, - apiScaffolder: apiScaffolder, + config: config, } } -func (s *initScaffolder) newUniverse() *model.Universe { - return model.NewUniverse( - model.WithConfig(s.config), - ) +// InjectFS implements plugins.Scaffolder +func (s *initScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs } // Scaffold implements Scaffolder func (s *initScaffolder) Scaffold() error { - if err := s.scaffold(); err != nil { - return err - } - if s.apiScaffolder != nil { - return s.apiScaffolder.Scaffold() - } - return nil -} + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + // NOTE: kubebuilder's default permissions are only for root users + machinery.WithDirectoryPermissions(0755), + machinery.WithFilePermissions(0644), + machinery.WithConfig(s.config), + ) -func (s *initScaffolder) scaffold() error { if err := os.MkdirAll(chartutil.HelmChartsDir, 0755); err != nil { return err } - return machinery.NewScaffold().Execute( - s.newUniverse(), + return scaffold.Execute( &templates.Dockerfile{ HelmOperatorVersion: helmOperatorVersion, }, @@ -91,21 +83,7 @@ func (s *initScaffolder) scaffold() error { HelmOperatorVersion: helmOperatorVersion, }, &templates.Watches{}, - &rbac.AuthProxyRole{}, - &rbac.AuthProxyRoleBinding{}, - &rbac.AuthProxyService{}, - &rbac.ClientClusterRole{}, - &rbac.Kustomization{}, - &rbac.LeaderElectionRole{}, - &rbac.LeaderElectionRoleBinding{}, &rbac.ManagerRole{}, - &rbac.ManagerRoleBinding{}, - &manager.Kustomization{}, - &manager.Manager{Image: imageName}, - &prometheus.Kustomization{}, - &prometheus.ServiceMonitor{}, - &kdefault.AuthProxyPatch{}, - &kdefault.Kustomization{}, ) } diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/crd/crd.go b/pkg/plugins/v1/scaffolds/internal/templates/config/crd/crd.go index 842a1ae0..85f81da3 100644 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/crd/crd.go +++ b/pkg/plugins/v1/scaffolds/internal/templates/config/crd/crd.go @@ -15,47 +15,40 @@ package crd import ( - "errors" "fmt" "path/filepath" "github.com/kr/text" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &CRD{} +var _ machinery.Template = &CRD{} // CRD scaffolds a manifest for CRD sample. type CRD struct { - file.TemplateMixin - file.ResourceMixin - - CRDVersion string + machinery.TemplateMixin + machinery.ResourceMixin } -// SetTemplateDefaults implements input.Template +// SetTemplateDefaults implements machinery.Template func (f *CRD) SetTemplateDefaults() error { if f.Path == "" { f.Path = filepath.Join("config", "crd", "bases", fmt.Sprintf("%s_%%[plural].yaml", f.Resource.QualifiedGroup())) } f.Path = f.Resource.Replacer().Replace(f.Path) - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error - if f.CRDVersion == "" { - f.CRDVersion = "v1" - } else if f.CRDVersion != "v1" && f.CRDVersion != "v1beta1" { - return errors.New("the CRD version value must be either 'v1' or 'v1beta1'") - } f.TemplateBody = fmt.Sprintf(crdTemplate, text.Indent(openAPIV3SchemaTemplate, " "), text.Indent(openAPIV3SchemaTemplate, " "), ) + return nil } const crdTemplate = `--- -apiVersion: apiextensions.k8s.io/{{ .CRDVersion }} +apiVersion: apiextensions.k8s.io/{{ .Resource.API.CRDVersion }} kind: CustomResourceDefinition metadata: name: {{ .Resource.Plural }}.{{ .Resource.QualifiedGroup }} @@ -67,7 +60,7 @@ spec: plural: {{ .Resource.Plural }} singular: {{ .Resource.Kind | lower }} scope: Namespaced -{{- if eq .CRDVersion "v1beta1" }} +{{- if eq .Resource.API.CRDVersion "v1beta1" }} subresources: status: {} validation: @@ -75,13 +68,13 @@ spec: {{- end }} versions: - name: {{ .Resource.Version }} -{{- if eq .CRDVersion "v1" }} +{{- if eq .Resource.API.CRDVersion "v1" }} schema: %s {{- end }} served: true storage: true -{{- if eq .CRDVersion "v1" }} +{{- if eq .Resource.API.CRDVersion "v1" }} subresources: status: {} {{- end }} diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/crd/kustomization.go b/pkg/plugins/v1/scaffolds/internal/templates/config/crd/kustomization.go index 7c0cc71d..871c00e1 100644 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/crd/kustomization.go +++ b/pkg/plugins/v1/scaffolds/internal/templates/config/crd/kustomization.go @@ -21,28 +21,27 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Kustomization{} -var _ file.Inserter = &Kustomization{} +var ( + _ machinery.Template = &Kustomization{} + _ machinery.Inserter = &Kustomization{} +) // Kustomization scaffolds the kustomization file in manager folder. type Kustomization struct { - file.TemplateMixin - file.ResourceMixin + machinery.TemplateMixin + machinery.ResourceMixin } -// SetTemplateDefaults implements file.Template +// SetTemplateDefaults implements machinery.Template func (f *Kustomization) SetTemplateDefaults() error { if f.Path == "" { f.Path = filepath.Join("config", "crd", "kustomization.yaml") } - f.Path = f.Resource.Replacer().Replace(f.Path) - f.TemplateBody = fmt.Sprintf(kustomizationTemplate, - file.NewMarkerFor(f.Path, resourceMarker), - ) + f.TemplateBody = fmt.Sprintf(kustomizationTemplate, machinery.NewMarkerFor(f.Path, resourceMarker)) return nil } @@ -51,10 +50,10 @@ const ( resourceMarker = "crdkustomizeresource" ) -// GetMarkers implements file.Inserter -func (f *Kustomization) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(f.Path, resourceMarker), +// GetMarkers implements machinery.Inserter +func (f *Kustomization) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, resourceMarker), } } @@ -63,20 +62,14 @@ const ( ` ) -// GetCodeFragments implements file.Inserter -func (f *Kustomization) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 3) - - // Generate resource code fragments - res := make([]string, 0) - res = append(res, fmt.Sprintf(resourceCodeFragment, f.Resource.QualifiedGroup(), f.Resource.Plural)) - - // Only store code fragments in the map if the slices are non-empty - if len(res) != 0 { - fragments[file.NewMarkerFor(f.Path, resourceMarker)] = res +// GetCodeFragments implements machinery.Inserter +func (f *Kustomization) GetCodeFragments() machinery.CodeFragmentsMap { + return machinery.CodeFragmentsMap{ + // Generate resource code fragments + machinery.NewMarkerFor(f.Path, resourceMarker): []string{ + fmt.Sprintf(resourceCodeFragment, f.Resource.QualifiedGroup(), f.Resource.Plural), + }, } - - return fragments } var kustomizationTemplate = `# This kustomization.yaml is not intended to be run by itself, diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go b/pkg/plugins/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go deleted file mode 100644 index 5c3a1086..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/kdefault/auth_proxy_patch.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. -Modifications copyright 2020 The Operator-SDK Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package kdefault - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &AuthProxyPatch{} - -// AuthProxyPatch scaffolds the patch file for enabling -// prometheus metrics for manager Pod. -type AuthProxyPatch struct { - file.TemplateMixin - file.ProjectNameMixin -} - -// SetTemplateDefaults implements input.Template -func (f *AuthProxyPatch) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "default", "manager_auth_proxy_patch.yaml") - } - - f.TemplateBody = kustomizeAuthProxyPatchTemplate - - f.IfExistsAction = file.Error - - return nil -} - -const kustomizeAuthProxyPatchTemplate = `# This patch inject a sidecar container which is a HTTP proxy for the -# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system -spec: - template: - spec: - containers: - - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 - args: - - "--secure-listen-address=0.0.0.0:8443" - - "--upstream=http://127.0.0.1:8080/" - - "--logtostderr=true" - - "--v=10" - ports: - - containerPort: 8443 - name: https - - name: manager - args: - - "--metrics-addr=127.0.0.1:8080" - - "--enable-leader-election" - - "--leader-election-id={{ .ProjectName }}" -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/kdefault/kustomization.go b/pkg/plugins/v1/scaffolds/internal/templates/config/kdefault/kustomization.go deleted file mode 100644 index a5745e7c..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/kdefault/kustomization.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. -Modifications copyright 2020 The Operator-SDK Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package kdefault - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &Kustomization{} - -// Kustomization scaffolds the Kustomization file for the default overlay -type Kustomization struct { - file.TemplateMixin - file.ProjectNameMixin -} - -// SetTemplateDefaults implements input.Template -func (f *Kustomization) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "default", "kustomization.yaml") - } - - f.TemplateBody = kustomizeTemplate - - f.IfExistsAction = file.Error - - return nil -} - -const kustomizeTemplate = `# Adds namespace to all resources. -namespace: {{ .ProjectName }}-system - -# Value of this field is prepended to the -# names of all resources, e.g. a deployment named -# "wordpress" becomes "alices-wordpress". -# Note that it should also match with the prefix (text before '-') of the namespace -# field above. -namePrefix: {{ .ProjectName }}- - -# Labels to add to all resources and selectors. -#commonLabels: -# someName: someValue - -bases: -- ../crd -- ../rbac -- ../manager -# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. -#- ../prometheus - -patchesStrategicMerge: - # Protect the /metrics endpoint by putting it behind auth. - # If you want your controller-manager to expose the /metrics - # endpoint w/o any authn/z, please comment the following line. -- manager_auth_proxy_patch.yaml -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/manager/kustomization.go b/pkg/plugins/v1/scaffolds/internal/templates/config/manager/kustomization.go deleted file mode 100644 index 93dcfafa..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/manager/kustomization.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package manager - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &Kustomization{} - -// Kustomization scaffolds the Kustomization file in manager folder. -type Kustomization struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *Kustomization) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "manager", "kustomization.yaml") - } - - f.TemplateBody = kustomizeManagerTemplate - - f.IfExistsAction = file.Error - - return nil -} - -const kustomizeManagerTemplate = `resources: -- manager.yaml -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/manager/manager.go b/pkg/plugins/v1/scaffolds/internal/templates/config/manager/manager.go deleted file mode 100644 index 6c59d16f..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/manager/manager.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. -Modifications copyright 2020 The Operator-SDK Authors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package manager - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &Manager{} - -// Manager scaffolds yaml config for the manager. -type Manager struct { - file.TemplateMixin - file.ProjectNameMixin - - // Image is controller manager image name - Image string -} - -// SetTemplateDefaults implements input.Template -func (f *Manager) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "manager", "manager.yaml") - } - - f.TemplateBody = managerTemplate - - return nil -} - -const managerTemplate = `apiVersion: v1 -kind: Namespace -metadata: - labels: - control-plane: controller-manager - name: system ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system - labels: - control-plane: controller-manager -spec: - selector: - matchLabels: - control-plane: controller-manager - replicas: 1 - template: - metadata: - labels: - control-plane: controller-manager - spec: - containers: - - image: {{ .Image }} - args: - - "--enable-leader-election" - - "--leader-election-id={{ .ProjectName }}" - name: manager - livenessProbe: - httpGet: - path: /healthz - port: 8081 - initialDelaySeconds: 15 - periodSeconds: 20 - readinessProbe: - httpGet: - path: /readyz - port: 8081 - initialDelaySeconds: 5 - periodSeconds: 10 - resources: - limits: - cpu: 100m - memory: 90Mi - requests: - cpu: 100m - memory: 60Mi - terminationGracePeriodSeconds: 10 -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/prometheus/kustomization.go b/pkg/plugins/v1/scaffolds/internal/templates/config/prometheus/kustomization.go deleted file mode 100644 index a794eea1..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/prometheus/kustomization.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package prometheus - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &Kustomization{} - -// Kustomization scaffolds the kustomizaiton in the prometheus folder -type Kustomization struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *Kustomization) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "prometheus", "kustomization.yaml") - } - - f.TemplateBody = kustomizationTemplate - - return nil -} - -const kustomizationTemplate = `resources: -- monitor.yaml -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/prometheus/monitor.go b/pkg/plugins/v1/scaffolds/internal/templates/config/prometheus/monitor.go deleted file mode 100644 index d092c6b8..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/prometheus/monitor.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package prometheus - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &ServiceMonitor{} - -// ServiceMonitor scaffolds an issuer CR and a certificate CR -type ServiceMonitor struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *ServiceMonitor) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "prometheus", "monitor.yaml") - } - - f.TemplateBody = serviceMonitorTemplate - - return nil -} - -const serviceMonitorTemplate = ` -# Prometheus Monitor Service (Metrics) -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - control-plane: controller-manager - name: controller-manager-metrics-monitor - namespace: system -spec: - endpoints: - - path: /metrics - port: https - selector: - matchLabels: - control-plane: controller-manager -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role.go deleted file mode 100644 index 2fb69db5..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &AuthProxyRole{} - -// AuthProxyRole scaffolds the config/rbac/auth_proxy_role.yaml file -type AuthProxyRole struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *AuthProxyRole) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "auth_proxy_role.yaml") - } - - f.TemplateBody = proxyRoleTemplate - - return nil -} - -const proxyRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: proxy-role -rules: -- apiGroups: ["authentication.k8s.io"] - resources: - - tokenreviews - verbs: ["create"] -- apiGroups: ["authorization.k8s.io"] - resources: - - subjectaccessreviews - verbs: ["create"] -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go deleted file mode 100644 index 5c52caec..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &AuthProxyRoleBinding{} - -// AuthProxyRoleBinding scaffolds the config/rbac/auth_proxy_role_binding_rbac.yaml file -type AuthProxyRoleBinding struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *AuthProxyRoleBinding) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "auth_proxy_role_binding.yaml") - } - - f.TemplateBody = proxyRoleBindinggTemplate - - return nil -} - -const proxyRoleBindinggTemplate = `apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: proxy-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: proxy-role -subjects: -- kind: ServiceAccount - name: default - namespace: system -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_service.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_service.go deleted file mode 100644 index 25a0a52a..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/auth_proxy_service.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &AuthProxyService{} - -// AuthProxyService scaffolds the config/rbac/auth_proxy_service.yaml file -type AuthProxyService struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *AuthProxyService) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "auth_proxy_service.yaml") - } - - f.TemplateBody = authProxyServiceTemplate - - return nil -} - -const authProxyServiceTemplate = `apiVersion: v1 -kind: Service -metadata: - labels: - control-plane: controller-manager - name: controller-manager-metrics-service - namespace: system -spec: - ports: - - name: https - port: 8443 - targetPort: https - selector: - control-plane: controller-manager -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/client_cluster_role.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/client_cluster_role.go deleted file mode 100644 index 6553210c..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/client_cluster_role.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &ClientClusterRole{} - -// ClientClusterRole scaffolds the config/rbac/client_clusterrole.yaml file -type ClientClusterRole struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *ClientClusterRole) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "auth_proxy_client_clusterrole.yaml") - } - - f.TemplateBody = clientClusterRoleTemplate - - return nil -} - -const clientClusterRoleTemplate = `apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: metrics-reader -rules: -- nonResourceURLs: ["/metrics"] - verbs: ["get"] -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/crd_editor_rbac.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/crd_editor_rbac.go deleted file mode 100644 index 44b02767..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/crd_editor_rbac.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &CRDEditorRole{} - -// CRDEditorRole scaffolds the config/rbac/_editor_role.yaml -type CRDEditorRole struct { - file.TemplateMixin - file.ResourceMixin -} - -// SetTemplateDefaults implements input.Template -func (f *CRDEditorRole) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "%[kind]_editor_role.yaml") - } - f.Path = f.Resource.Replacer().Replace(f.Path) - - f.TemplateBody = crdRoleEditorTemplate - - return nil -} - -const crdRoleEditorTemplate = `# permissions for end users to edit {{ .Resource.Plural }}. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ lower .Resource.Kind }}-editor-role -rules: -- apiGroups: - - {{ .Resource.QualifiedGroup }} - resources: - - {{ .Resource.Plural }} - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - {{ .Resource.QualifiedGroup }} - resources: - - {{ .Resource.Plural }}/status - verbs: - - get -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/crd_viewer_rbac.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/crd_viewer_rbac.go deleted file mode 100644 index 3dee9614..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/crd_viewer_rbac.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &CRDViewerRole{} - -// CRDViewerRole scaffolds the config/rbac/_viewer_role.yaml -type CRDViewerRole struct { - file.TemplateMixin - file.ResourceMixin -} - -// SetTemplateDefaults implements input.Template -func (f *CRDViewerRole) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "%[kind]_viewer_role.yaml") - } - f.Path = f.Resource.Replacer().Replace(f.Path) - - f.TemplateBody = crdRoleViewerTemplate - - return nil -} - -const crdRoleViewerTemplate = `# permissions for end users to view {{ .Resource.Plural }}. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: {{ lower .Resource.Kind }}-viewer-role -rules: -- apiGroups: - - {{ .Resource.QualifiedGroup }} - resources: - - {{ .Resource.Plural }} - verbs: - - get - - list - - watch -- apiGroups: - - {{ .Resource.QualifiedGroup }} - resources: - - {{ .Resource.Plural }}/status - verbs: - - get -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/kustomization.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/kustomization.go deleted file mode 100644 index 74c3ef6e..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/kustomization.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &Kustomization{} - -// Kustomization scaffolds the Kustomization file in rbac folder. -type Kustomization struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *Kustomization) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "kustomization.yaml") - } - - f.TemplateBody = kustomizeRBACTemplate - - f.IfExistsAction = file.Error - - return nil -} - -const kustomizeRBACTemplate = `resources: -- role.yaml -- role_binding.yaml -- leader_election_role.yaml -- leader_election_role_binding.yaml -# Comment the following 4 lines if you want to disable -# the auth proxy (https://github.com/brancz/kube-rbac-proxy) -# which protects your /metrics endpoint. -- auth_proxy_service.yaml -- auth_proxy_role.yaml -- auth_proxy_role_binding.yaml -- auth_proxy_client_clusterrole.yaml -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/leader_election_role.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/leader_election_role.go deleted file mode 100644 index f029a428..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/leader_election_role.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &LeaderElectionRole{} - -// LeaderElectionRole scaffolds the config/rbac/leader_election_role.yaml file -type LeaderElectionRole struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *LeaderElectionRole) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "leader_election_role.yaml") - } - - f.TemplateBody = leaderElectionRoleTemplate - - return nil -} - -const leaderElectionRoleTemplate = `# permissions to do leader election. -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: leader-election-role -rules: -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - "" - resources: - - events - verbs: - - create - - patch -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go deleted file mode 100644 index 994c3179..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &LeaderElectionRoleBinding{} - -// LeaderElectionRoleBinding scaffolds the config/rbac/leader_election_role_binding.yaml file -type LeaderElectionRoleBinding struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *LeaderElectionRoleBinding) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "leader_election_role_binding.yaml") - } - - f.TemplateBody = leaderElectionRoleBindingTemplate - - return nil -} - -const leaderElectionRoleBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: leader-election-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: leader-election-role -subjects: -- kind: ServiceAccount - name: default - namespace: system -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/manager_role.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/manager_role.go index 8472ff19..e5140183 100644 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/manager_role.go +++ b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/manager_role.go @@ -32,36 +32,34 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/discovery" crconfig "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/yaml" ) -var _ file.Template = &ManagerRole{} +var _ machinery.Template = &ManagerRole{} var defaultRoleFile = filepath.Join("config", "rbac", "role.yaml") // ManagerRole scaffolds the role.yaml file type ManagerRole struct { - file.TemplateMixin + machinery.TemplateMixin } -// SetTemplateDefaults implements input.Template +// SetTemplateDefaults implements machinery.Template func (f *ManagerRole) SetTemplateDefaults() error { if f.Path == "" { f.Path = defaultRoleFile } - f.TemplateBody = fmt.Sprintf(roleTemplate, - file.NewMarkerFor(f.Path, rulesMarker), - ) + f.TemplateBody = fmt.Sprintf(roleTemplate, machinery.NewMarkerFor(f.Path, rulesMarker)) + return nil } -var _ file.Inserter = &ManagerRoleUpdater{} +var _ machinery.Inserter = &ManagerRoleUpdater{} type ManagerRoleUpdater struct { - file.TemplateMixin - file.ResourceMixin + machinery.ResourceMixin Chart *chart.Chart SkipDefaultRules bool @@ -72,22 +70,22 @@ func (*ManagerRoleUpdater) GetPath() string { return defaultRoleFile } -func (*ManagerRoleUpdater) GetIfExistsAction() file.IfExistsAction { - return file.Overwrite +func (*ManagerRoleUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile } const ( rulesMarker = "rules" ) -func (f *ManagerRoleUpdater) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(defaultRoleFile, rulesMarker), +func (f *ManagerRoleUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(defaultRoleFile, rulesMarker), } } -func (f *ManagerRoleUpdater) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 1) +func (f *ManagerRoleUpdater) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 1) // If resource is not being provided we are creating the file, not updating it if f.Resource == nil { @@ -113,7 +111,7 @@ func (f *ManagerRoleUpdater) GetCodeFragments() file.CodeFragmentsMap { rules := []string{buf.String()} if len(rules) != 0 { - fragments[file.NewMarkerFor(defaultRoleFile, rulesMarker)] = rules + fragments[machinery.NewMarkerFor(defaultRoleFile, rulesMarker)] = rules } return fragments } diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/manager_role_binding.go b/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/manager_role_binding.go deleted file mode 100644 index 544f4656..00000000 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/rbac/manager_role_binding.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "path/filepath" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" -) - -var _ file.Template = &ManagerRoleBinding{} - -// ManagerRoleBinding scaffolds the config/rbac/role_binding.yaml file -type ManagerRoleBinding struct { - file.TemplateMixin -} - -// SetTemplateDefaults implements input.Template -func (f *ManagerRoleBinding) SetTemplateDefaults() error { - if f.Path == "" { - f.Path = filepath.Join("config", "rbac", "role_binding.yaml") - } - - f.TemplateBody = managerBindingTemplate - - return nil -} - -const managerBindingTemplate = `apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: manager-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: manager-role -subjects: -- kind: ServiceAccount - name: default - namespace: system -` diff --git a/pkg/plugins/v1/scaffolds/internal/templates/config/samples/crd_sample.go b/pkg/plugins/v1/scaffolds/internal/templates/config/samples/custom_resources.go similarity index 70% rename from pkg/plugins/v1/scaffolds/internal/templates/config/samples/crd_sample.go rename to pkg/plugins/v1/scaffolds/internal/templates/config/samples/custom_resources.go index b33681fb..ec757433 100644 --- a/pkg/plugins/v1/scaffolds/internal/templates/config/samples/crd_sample.go +++ b/pkg/plugins/v1/scaffolds/internal/templates/config/samples/custom_resources.go @@ -24,31 +24,33 @@ import ( "text/template" "helm.sh/helm/v3/pkg/chart" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/yaml" ) -var _ file.Template = &CRDSample{} -var _ file.UseCustomFuncMap = &CRDSample{} +var ( + _ machinery.Template = &CustomResource{} + _ machinery.UseCustomFuncMap = &CustomResource{} +) -// CRDSample scaffolds a manifest for CRD sample. -type CRDSample struct { - file.TemplateMixin - file.ResourceMixin +// CustomResource scaffolds a custom resource sample manifest. +type CustomResource struct { + machinery.TemplateMixin + machinery.ResourceMixin ChartPath string Chart *chart.Chart Spec string } -// SetTemplateDefaults implements input.Template -func (f *CRDSample) SetTemplateDefaults() error { +// SetTemplateDefaults implements machinery.Template +func (f *CustomResource) SetTemplateDefaults() error { if f.Path == "" { f.Path = filepath.Join("config", "samples", "%[group]_%[version]_%[kind].yaml") } f.Path = f.Resource.Replacer().Replace(f.Path) - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.OverwriteFile if len(f.Spec) == 0 { f.Spec = defaultSpecTemplate @@ -65,7 +67,7 @@ func (f *CRDSample) SetTemplateDefaults() error { } } - f.TemplateBody = crdSampleTemplate + f.TemplateBody = customResourceTemplate return nil } @@ -74,9 +76,9 @@ func indent(spaces int, v string) string { return pad + strings.Replace(v, "\n", "\n"+pad, -1) } -// GetFuncMap implements file.UseCustomFuncMap -func (f *CRDSample) GetFuncMap() template.FuncMap { - fm := file.DefaultFuncMap() +// GetFuncMap implements machinery.UseCustomFuncMap +func (f *CustomResource) GetFuncMap() template.FuncMap { + fm := machinery.DefaultFuncMap() fm["indent"] = indent return fm } @@ -84,7 +86,7 @@ func (f *CRDSample) GetFuncMap() template.FuncMap { const defaultSpecTemplate = `foo: bar ` -const crdSampleTemplate = `apiVersion: {{ .Resource.QualifiedGroup }}/{{ .Resource.Version }} +const customResourceTemplate = `apiVersion: {{ .Resource.QualifiedGroup }}/{{ .Resource.Version }} kind: {{ .Resource.Kind }} metadata: name: {{ lower .Resource.Kind }}-sample diff --git a/pkg/plugins/v1/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/v1/scaffolds/internal/templates/dockerfile.go index 0803e936..55c04a62 100644 --- a/pkg/plugins/v1/scaffolds/internal/templates/dockerfile.go +++ b/pkg/plugins/v1/scaffolds/internal/templates/dockerfile.go @@ -17,20 +17,20 @@ package templates import ( "errors" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Dockerfile{} +var _ machinery.Template = &Dockerfile{} // Dockerfile scaffolds a Dockerfile for building a main type Dockerfile struct { - file.TemplateMixin + machinery.TemplateMixin - // HelmOperatorVersion is the version of the Dockerfile's base image. + // HelmOperatorVersion is the version of the Dockerfile base image. HelmOperatorVersion string } -// SetTemplateDefaults implements input.Template +// SetTemplateDefaults implements machinery.Template func (f *Dockerfile) SetTemplateDefaults() error { if f.Path == "" { f.Path = "Dockerfile" @@ -46,7 +46,7 @@ func (f *Dockerfile) SetTemplateDefaults() error { } const dockerfileTemplate = `# Build the manager binary -FROM quay.io/joelanford/helm-operator:{{.HelmOperatorVersion}} +FROM quay.io/operator-framework/helm-operator:{{.HelmOperatorVersion}} ENV HOME=/opt/helm COPY watches.yaml ${HOME}/watches.yaml diff --git a/pkg/plugins/v1/scaffolds/internal/templates/gitignore.go b/pkg/plugins/v1/scaffolds/internal/templates/gitignore.go index 64cc86d9..0d7290b5 100644 --- a/pkg/plugins/v1/scaffolds/internal/templates/gitignore.go +++ b/pkg/plugins/v1/scaffolds/internal/templates/gitignore.go @@ -18,17 +18,17 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &GitIgnore{} +var _ machinery.Template = &GitIgnore{} // GitIgnore scaffolds the .gitignore file type GitIgnore struct { - file.TemplateMixin + machinery.TemplateMixin } -// SetTemplateDefaults implements input.Template +// SetTemplateDefaults implements machinery.Template func (f *GitIgnore) SetTemplateDefaults() error { if f.Path == "" { f.Path = ".gitignore" diff --git a/pkg/plugins/v1/scaffolds/internal/templates/makefile.go b/pkg/plugins/v1/scaffolds/internal/templates/makefile.go index a38f8a6e..814b2816 100644 --- a/pkg/plugins/v1/scaffolds/internal/templates/makefile.go +++ b/pkg/plugins/v1/scaffolds/internal/templates/makefile.go @@ -20,14 +20,14 @@ package templates import ( "errors" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Makefile{} +var _ machinery.Template = &Makefile{} // Makefile scaffolds the Makefile type Makefile struct { - file.TemplateMixin + machinery.TemplateMixin // Image is controller manager image name Image string @@ -39,7 +39,7 @@ type Makefile struct { HelmOperatorVersion string } -// SetTemplateDefaults implements input.Template +// SetTemplateDefaults implements machinery.Template func (f *Makefile) SetTemplateDefaults() error { if f.Path == "" { f.Path = "Makefile" @@ -47,7 +47,7 @@ func (f *Makefile) SetTemplateDefaults() error { f.TemplateBody = makefileTemplate - f.IfExistsAction = file.Error + f.IfExistsAction = machinery.Error if f.Image == "" { f.Image = "controller:latest" @@ -70,42 +70,54 @@ IMG ?= {{ .Image }} all: docker-build -# Run against the configured Kubernetes cluster in ~/.kube/config -run: helm-operator +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Build + +run: helm-operator ## Run against the configured Kubernetes cluster in ~/.kube/config $(HELM_OPERATOR) run -# Install CRDs into a cluster -install: kustomize +docker-build: ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: kustomize +uninstall: kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: kustomize +deploy: kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} $(KUSTOMIZE) build config/default | kubectl apply -f - -# Undeploy controller in the configured Kubernetes cluster in ~/.kube/config -undeploy: kustomize +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/default | kubectl delete -f - -# Build the docker image -docker-build: - docker build -t ${IMG} . - -# Push the docker image -docker-push: - docker push ${IMG} - OS := $(shell uname -s | tr '[:upper:]' '[:lower:]') ARCH := $(shell uname -m | sed 's/x86_64/amd64/') -# Download kustomize locally if necessary, preferring the $(pwd)/bin path over global if both exist. .PHONY: kustomize KUSTOMIZE = $(shell pwd)/bin/kustomize -kustomize: +kustomize: ## Download kustomize locally if necessary. ifeq (,$(wildcard $(KUSTOMIZE))) ifeq (,$(shell which kustomize 2>/dev/null)) @{ \ @@ -119,10 +131,9 @@ KUSTOMIZE = $(shell which kustomize) endif endif -# Download helm-operator locally if necessary, preferring the $(pwd)/bin path over global if both exist. .PHONY: helm-operator HELM_OPERATOR = $(shell pwd)/bin/helm-operator -helm-operator: +helm-operator: ## Download helm-operator locally if necessary, preferring the $(pwd)/bin path over global if both exist. ifeq (,$(wildcard $(HELM_OPERATOR))) ifeq (,$(shell which helm-operator 2>/dev/null)) @{ \ diff --git a/pkg/plugins/v1/scaffolds/internal/templates/watches.go b/pkg/plugins/v1/scaffolds/internal/templates/watches.go index 5269ae1f..2297cc03 100644 --- a/pkg/plugins/v1/scaffolds/internal/templates/watches.go +++ b/pkg/plugins/v1/scaffolds/internal/templates/watches.go @@ -17,35 +17,34 @@ package templates import ( "fmt" - "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" ) -var _ file.Template = &Watches{} +var _ machinery.Template = &Watches{} const defaultWatchesFile = "watches.yaml" // Watches scaffolds the watches.yaml file type Watches struct { - file.TemplateMixin + machinery.TemplateMixin } -// SetTemplateDefaults implements input.Template +// SetTemplateDefaults implements machinery.Template func (f *Watches) SetTemplateDefaults() error { if f.Path == "" { f.Path = defaultWatchesFile } f.TemplateBody = fmt.Sprintf(watchesTemplate, - file.NewMarkerFor(f.Path, watchMarker), + machinery.NewMarkerFor(f.Path, watchMarker), ) return nil } -var _ file.Inserter = &WatchesUpdater{} +var _ machinery.Inserter = &WatchesUpdater{} type WatchesUpdater struct { - file.TemplateMixin - file.ResourceMixin + machinery.ResourceMixin ChartPath string } @@ -54,22 +53,22 @@ func (*WatchesUpdater) GetPath() string { return defaultWatchesFile } -func (*WatchesUpdater) GetIfExistsAction() file.IfExistsAction { - return file.Overwrite +func (*WatchesUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile } const ( watchMarker = "watch" ) -func (f *WatchesUpdater) GetMarkers() []file.Marker { - return []file.Marker{ - file.NewMarkerFor(defaultWatchesFile, watchMarker), +func (f *WatchesUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(defaultWatchesFile, watchMarker), } } -func (f *WatchesUpdater) GetCodeFragments() file.CodeFragmentsMap { - fragments := make(file.CodeFragmentsMap, 1) +func (f *WatchesUpdater) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 1) // If resource is not being provided we are creating the file, not updating it if f.Resource == nil { @@ -82,7 +81,7 @@ func (f *WatchesUpdater) GetCodeFragments() file.CodeFragmentsMap { fmt.Sprintf(watchFragment, f.Resource.QualifiedGroup(), f.Resource.Version, f.Resource.Kind, f.ChartPath)) if len(watches) != 0 { - fragments[file.NewMarkerFor(defaultWatchesFile, watchMarker)] = watches + fragments[machinery.NewMarkerFor(defaultWatchesFile, watchMarker)] = watches } return fragments } From 31b6b65a9ac07da28d084a0c59f87450d119f8f4 Mon Sep 17 00:00:00 2001 From: varshaprasad96 Date: Fri, 25 Jun 2021 13:34:46 -0700 Subject: [PATCH 2/2] Remove machinery package Signed-off-by: varshaprasad96 --- go.mod | 2 - .../internal/kubebuilder/machinery/errors.go | 145 ----- .../kubebuilder/machinery/errors_test.go | 55 -- .../internal/kubebuilder/machinery/file.go | 43 -- .../kubebuilder/machinery/filesystem.go | 26 - .../internal/kubebuilder/machinery/funcmap.go | 47 -- .../kubebuilder/machinery/funcmap_test.go | 45 -- .../kubebuilder/machinery/injector.go | 66 --- .../kubebuilder/machinery/injector_test.go | 295 ---------- .../kubebuilder/machinery/interfaces.go | 104 ---- .../machinery/machinery_suite_test.go | 29 - .../internal/kubebuilder/machinery/marker.go | 71 --- .../kubebuilder/machinery/marker_test.go | 47 -- .../internal/kubebuilder/machinery/mixins.go | 153 ------ .../kubebuilder/machinery/mixins_test.go | 188 ------- .../kubebuilder/machinery/scaffold.go | 443 --------------- .../kubebuilder/machinery/scaffold_test.go | 505 ------------------ 17 files changed, 2264 deletions(-) delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/errors.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/errors_test.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/file.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/filesystem.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/funcmap.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/funcmap_test.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/injector.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/injector_test.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/interfaces.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/machinery_suite_test.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/marker.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/marker_test.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/mixins.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/mixins_test.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/scaffold.go delete mode 100644 pkg/plugins/internal/kubebuilder/machinery/scaffold_test.go diff --git a/go.mod b/go.mod index 1101b475..c8b3772c 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,9 @@ require ( github.com/operator-framework/operator-lib v0.3.0 github.com/prometheus/client_golang v1.7.1 github.com/sirupsen/logrus v1.7.0 - github.com/spf13/afero v1.2.2 github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.6.1 - golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e gomodules.xyz/jsonpatch/v2 v2.1.0 helm.sh/helm/v3 v3.5.0 k8s.io/api v0.20.4 diff --git a/pkg/plugins/internal/kubebuilder/machinery/errors.go b/pkg/plugins/internal/kubebuilder/machinery/errors.go deleted file mode 100644 index af015ac3..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/errors.go +++ /dev/null @@ -1,145 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "fmt" -) - -// This file contains the errors returned by the scaffolding machinery -// They are exported to be able to check which kind of error was returned - -// ValidateError is a wrapper error that will be used for errors returned by RequiresValidation.Validate -type ValidateError struct { - error -} - -// Unwrap implements Wrapper interface -func (e ValidateError) Unwrap() error { - return e.error -} - -// SetTemplateDefaultsError is a wrapper error that will be used for errors returned by Template.SetTemplateDefaults -type SetTemplateDefaultsError struct { - error -} - -// Unwrap implements Wrapper interface -func (e SetTemplateDefaultsError) Unwrap() error { - return e.error -} - -// ExistsFileError is a wrapper error that will be used for errors when checking for a file existence -type ExistsFileError struct { - error -} - -// Unwrap implements Wrapper interface -func (e ExistsFileError) Unwrap() error { - return e.error -} - -// OpenFileError is a wrapper error that will be used for errors when opening a file -type OpenFileError struct { - error -} - -// Unwrap implements Wrapper interface -func (e OpenFileError) Unwrap() error { - return e.error -} - -// CreateDirectoryError is a wrapper error that will be used for errors when creating a directory -type CreateDirectoryError struct { - error -} - -// Unwrap implements Wrapper interface -func (e CreateDirectoryError) Unwrap() error { - return e.error -} - -// CreateFileError is a wrapper error that will be used for errors when creating a file -type CreateFileError struct { - error -} - -// Unwrap implements Wrapper interface -func (e CreateFileError) Unwrap() error { - return e.error -} - -// ReadFileError is a wrapper error that will be used for errors when reading a file -type ReadFileError struct { - error -} - -// Unwrap implements Wrapper interface -func (e ReadFileError) Unwrap() error { - return e.error -} - -// WriteFileError is a wrapper error that will be used for errors when writing a file -type WriteFileError struct { - error -} - -// Unwrap implements Wrapper interface -func (e WriteFileError) Unwrap() error { - return e.error -} - -// CloseFileError is a wrapper error that will be used for errors when closing a file -type CloseFileError struct { - error -} - -// Unwrap implements Wrapper interface -func (e CloseFileError) Unwrap() error { - return e.error -} - -// ModelAlreadyExistsError is returned if the file is expected not to exist but a previous model does -type ModelAlreadyExistsError struct { - path string -} - -// Error implements error interface -func (e ModelAlreadyExistsError) Error() string { - return fmt.Sprintf("failed to create %s: model already exists", e.path) -} - -// UnknownIfExistsActionError is returned if the if-exists-action is unknown -type UnknownIfExistsActionError struct { - path string - ifExistsAction IfExistsAction -} - -// Error implements error interface -func (e UnknownIfExistsActionError) Error() string { - return fmt.Sprintf("unknown behavior if file exists (%d) for %s", e.ifExistsAction, e.path) -} - -// FileAlreadyExistsError is returned if the file is expected not to exist but it does -type FileAlreadyExistsError struct { - path string -} - -// Error implements error interface -func (e FileAlreadyExistsError) Error() string { - return fmt.Sprintf("failed to create %s: file already exists", e.path) -} diff --git a/pkg/plugins/internal/kubebuilder/machinery/errors_test.go b/pkg/plugins/internal/kubebuilder/machinery/errors_test.go deleted file mode 100644 index 2c4d682e..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/errors_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "errors" - "path/filepath" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" -) - -var _ = Describe("Errors", func() { - var ( - path = filepath.Join("path", "to", "file") - testErr = errors.New("test error") - ) - - DescribeTable("should contain the wrapped error", - func(err error) { - Expect(errors.Is(err, testErr)).To(BeTrue()) - }, - Entry("for validate errors", ValidateError{testErr}), - Entry("for set template defaults errors", SetTemplateDefaultsError{testErr}), - Entry("for file existence errors", ExistsFileError{testErr}), - Entry("for file opening errors", OpenFileError{testErr}), - Entry("for directory creation errors", CreateDirectoryError{testErr}), - Entry("for file creation errors", CreateFileError{testErr}), - Entry("for file reading errors", ReadFileError{testErr}), - Entry("for file writing errors", WriteFileError{testErr}), - Entry("for file closing errors", CloseFileError{testErr}), - ) - - // NOTE: the following test increases coverage - It("should print a descriptive error message", func() { - Expect(ModelAlreadyExistsError{path}.Error()).To(ContainSubstring("model already exists")) - Expect(UnknownIfExistsActionError{path, -1}.Error()).To(ContainSubstring("unknown behavior if file exists")) - Expect(FileAlreadyExistsError{path}.Error()).To(ContainSubstring("file already exists")) - }) -}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/file.go b/pkg/plugins/internal/kubebuilder/machinery/file.go deleted file mode 100644 index bf053d14..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/file.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -// IfExistsAction determines what to do if the scaffold file already exists -type IfExistsAction int - -const ( - // SkipFile skips the file and moves to the next one - SkipFile IfExistsAction = iota - - // Error returns an error and stops processing - Error - - // OverwriteFile truncates and overwrites the existing file - OverwriteFile -) - -// File describes a file that will be written -type File struct { - // Path is the file to write - Path string - - // Contents is the generated output - Contents string - - // IfExistsAction determines what to do if the file exists - IfExistsAction IfExistsAction -} diff --git a/pkg/plugins/internal/kubebuilder/machinery/filesystem.go b/pkg/plugins/internal/kubebuilder/machinery/filesystem.go deleted file mode 100644 index f28cfb8b..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/filesystem.go +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "github.com/spf13/afero" -) - -// Filesystem abstracts the underlying disk for scaffolding -type Filesystem struct { - FS afero.Fs -} diff --git a/pkg/plugins/internal/kubebuilder/machinery/funcmap.go b/pkg/plugins/internal/kubebuilder/machinery/funcmap.go deleted file mode 100644 index ac25e272..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/funcmap.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "fmt" - "hash/fnv" - "strings" - "text/template" -) - -// DefaultFuncMap returns the default template.FuncMap for rendering the template. -func DefaultFuncMap() template.FuncMap { - return template.FuncMap{ - "title": strings.Title, - "lower": strings.ToLower, - "isEmptyStr": isEmptyString, - "hashFNV": hashFNV, - } -} - -// isEmptyString returns whether the string is empty -func isEmptyString(s string) bool { - return s == "" -} - -// hashFNV will generate a random string useful for generating a unique string -func hashFNV(s string) string { - hasher := fnv.New32a() - // Hash.Write never returns an error - _, _ = hasher.Write([]byte(s)) - return fmt.Sprintf("%x", hasher.Sum(nil)) -} diff --git a/pkg/plugins/internal/kubebuilder/machinery/funcmap_test.go b/pkg/plugins/internal/kubebuilder/machinery/funcmap_test.go deleted file mode 100644 index 7bb33df4..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/funcmap_test.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" -) - -var _ = Describe("funcmap functions", func() { - Context("isEmptyString", func() { - It("should return true for empty strings", func() { - Expect(isEmptyString("")).To(BeTrue()) - }) - - DescribeTable("should return false for any other string", - func(str string) { Expect(isEmptyString(str)).To(BeFalse()) }, - Entry(`for "a"`, "a"), - Entry(`for "1"`, "1"), - Entry(`for "-"`, "-"), - Entry(`for "."`, "."), - ) - }) - - Context("hashFNV", func() { - It("should hash the input", func() { - Expect(hashFNV("test")).To(Equal("afd071e5")) - }) - }) -}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/injector.go b/pkg/plugins/internal/kubebuilder/machinery/injector.go deleted file mode 100644 index 5675a8a9..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/injector.go +++ /dev/null @@ -1,66 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -// injector is used to inject certain fields to file templates. -type injector struct { - // config stores the project configuration. - config config.Config - - // boilerplate is the copyright comment added at the top of scaffolded files. - boilerplate string - - // resource contains the information of the API that is being scaffolded. - resource *resource.Resource -} - -// injectInto injects fields from the universe into the builder -func (i injector) injectInto(builder Builder) { - // Inject project configuration - if i.config != nil { - if builderWithDomain, hasDomain := builder.(HasDomain); hasDomain { - builderWithDomain.InjectDomain(i.config.GetDomain()) - } - if builderWithRepository, hasRepository := builder.(HasRepository); hasRepository { - builderWithRepository.InjectRepository(i.config.GetRepository()) - } - if builderWithProjectName, hasProjectName := builder.(HasProjectName); hasProjectName { - builderWithProjectName.InjectProjectName(i.config.GetProjectName()) - } - if builderWithMultiGroup, hasMultiGroup := builder.(HasMultiGroup); hasMultiGroup { - builderWithMultiGroup.InjectMultiGroup(i.config.IsMultiGroup()) - } - if builderWithComponentConfig, hasComponentConfig := builder.(HasComponentConfig); hasComponentConfig { - builderWithComponentConfig.InjectComponentConfig(i.config.IsComponentConfig()) - } - } - // Inject boilerplate - if builderWithBoilerplate, hasBoilerplate := builder.(HasBoilerplate); hasBoilerplate { - builderWithBoilerplate.InjectBoilerplate(i.boilerplate) - } - // Inject resource - if i.resource != nil { - if builderWithResource, hasResource := builder.(HasResource); hasResource { - builderWithResource.InjectResource(i.resource) - } - } -} diff --git a/pkg/plugins/internal/kubebuilder/machinery/injector_test.go b/pkg/plugins/internal/kubebuilder/machinery/injector_test.go deleted file mode 100644 index 7c5c3038..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/injector_test.go +++ /dev/null @@ -1,295 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "sigs.k8s.io/kubebuilder/v3/pkg/config" - cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -type templateBase struct { - path string - ifExistsAction IfExistsAction -} - -func (t templateBase) GetPath() string { - return t.path -} - -func (t templateBase) GetIfExistsAction() IfExistsAction { - return t.ifExistsAction -} - -type templateWithDomain struct { - templateBase - domain string -} - -func (t *templateWithDomain) InjectDomain(domain string) { - t.domain = domain -} - -type templateWithRepository struct { - templateBase - repository string -} - -func (t *templateWithRepository) InjectRepository(repository string) { - t.repository = repository -} - -type templateWithProjectName struct { - templateBase - projectName string -} - -func (t *templateWithProjectName) InjectProjectName(projectName string) { - t.projectName = projectName -} - -type templateWithMultiGroup struct { - templateBase - multiGroup bool -} - -func (t *templateWithMultiGroup) InjectMultiGroup(multiGroup bool) { - t.multiGroup = multiGroup -} - -type templateWithComponentConfig struct { - templateBase - componentConfig bool -} - -func (t *templateWithComponentConfig) InjectComponentConfig(componentConfig bool) { - t.componentConfig = componentConfig -} - -type templateWithBoilerplate struct { - templateBase - boilerplate string -} - -func (t *templateWithBoilerplate) InjectBoilerplate(boilerplate string) { - t.boilerplate = boilerplate -} - -type templateWithResource struct { - templateBase - resource *resource.Resource -} - -func (t *templateWithResource) InjectResource(res *resource.Resource) { - t.resource = res -} - -var _ = Describe("injector", func() { - var tmp = templateBase{ - path: "my/path/to/file", - ifExistsAction: Error, - } - - Context("injectInto", func() { - Context("Config", func() { - var c config.Config - - BeforeEach(func() { - c = cfgv3.New() - }) - - Context("Domain", func() { - var template *templateWithDomain - - BeforeEach(func() { - template = &templateWithDomain{templateBase: tmp} - }) - - It("should not inject anything if the config is nil", func() { - injector{}.injectInto(template) - Expect(template.domain).To(Equal("")) - }) - - It("should not inject anything if the config doesn't have a domain set", func() { - injector{config: c}.injectInto(template) - Expect(template.domain).To(Equal("")) - }) - - It("should inject if the config has a domain set", func() { - const domain = "my.domain" - Expect(c.SetDomain(domain)).To(Succeed()) - - injector{config: c}.injectInto(template) - Expect(template.domain).To(Equal(domain)) - }) - }) - - Context("Repository", func() { - var template *templateWithRepository - - BeforeEach(func() { - template = &templateWithRepository{templateBase: tmp} - }) - - It("should not inject anything if the config is nil", func() { - injector{}.injectInto(template) - Expect(template.repository).To(Equal("")) - }) - - It("should not inject anything if the config doesn't have a repository set", func() { - injector{config: c}.injectInto(template) - Expect(template.repository).To(Equal("")) - }) - - It("should inject if the config has a repository set", func() { - const repo = "test" - Expect(c.SetRepository(repo)).To(Succeed()) - - injector{config: c}.injectInto(template) - Expect(template.repository).To(Equal(repo)) - }) - }) - - Context("Project name", func() { - var template *templateWithProjectName - - BeforeEach(func() { - template = &templateWithProjectName{templateBase: tmp} - }) - - It("should not inject anything if the config is nil", func() { - injector{}.injectInto(template) - Expect(template.projectName).To(Equal("")) - }) - - It("should not inject anything if the config doesn't have a project name set", func() { - injector{config: c}.injectInto(template) - Expect(template.projectName).To(Equal("")) - }) - - It("should inject if the config has a project name set", func() { - const projectName = "my project" - Expect(c.SetProjectName(projectName)).To(Succeed()) - - injector{config: c}.injectInto(template) - Expect(template.projectName).To(Equal(projectName)) - }) - }) - - Context("Multi-group", func() { - var template *templateWithMultiGroup - - BeforeEach(func() { - template = &templateWithMultiGroup{templateBase: tmp} - }) - - It("should not inject anything if the config is nil", func() { - injector{}.injectInto(template) - Expect(template.multiGroup).To(BeFalse()) - }) - - It("should not set the flag if the config doesn't have the multi-group flag set", func() { - injector{config: c}.injectInto(template) - Expect(template.multiGroup).To(BeFalse()) - }) - - It("should set the flag if the config has the multi-group flag set", func() { - Expect(c.SetMultiGroup()).To(Succeed()) - - injector{config: c}.injectInto(template) - Expect(template.multiGroup).To(BeTrue()) - }) - }) - - Context("Component config", func() { - var template *templateWithComponentConfig - - BeforeEach(func() { - template = &templateWithComponentConfig{templateBase: tmp} - }) - - It("should not inject anything if the config is nil", func() { - injector{}.injectInto(template) - Expect(template.componentConfig).To(BeFalse()) - }) - - It("should not set the flag if the config doesn't have the component config flag set", func() { - injector{config: c}.injectInto(template) - Expect(template.componentConfig).To(BeFalse()) - }) - - It("should set the flag if the config has the component config flag set", func() { - Expect(c.SetComponentConfig()).To(Succeed()) - - injector{config: c}.injectInto(template) - Expect(template.componentConfig).To(BeTrue()) - }) - }) - }) - - Context("Boilerplate", func() { - var template *templateWithBoilerplate - - BeforeEach(func() { - template = &templateWithBoilerplate{templateBase: tmp} - }) - - It("should not inject anything if no boilerplate was set", func() { - injector{}.injectInto(template) - Expect(template.boilerplate).To(Equal("")) - }) - - It("should inject if the a boilerplate was set", func() { - const boilerplate = `Copyright "The Kubernetes Authors"` - - injector{boilerplate: boilerplate}.injectInto(template) - Expect(template.boilerplate).To(Equal(boilerplate)) - }) - }) - - Context("Resource", func() { - var template *templateWithResource - - BeforeEach(func() { - template = &templateWithResource{templateBase: tmp} - }) - - It("should not inject anything if the resource is nil", func() { - injector{}.injectInto(template) - Expect(template.resource).To(BeNil()) - }) - - It("should inject if the config has a domain set", func() { - var res = &resource.Resource{ - GVK: resource.GVK{ - Group: "group", - Domain: "my.domain", - Version: "v1", - Kind: "Kind", - }, - } - - injector{resource: res}.injectInto(template) - Expect(template.resource).To(Equal(res)) - }) - - }) - }) -}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/interfaces.go b/pkg/plugins/internal/kubebuilder/machinery/interfaces.go deleted file mode 100644 index 73837dd8..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/interfaces.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "text/template" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -// Builder defines the basic methods that any file builder must implement -type Builder interface { - // GetPath returns the path to the file location - GetPath() string - // GetIfExistsAction returns the behavior when creating a file that already exists - GetIfExistsAction() IfExistsAction -} - -// RequiresValidation is a file builder that requires validation -type RequiresValidation interface { - Builder - // Validate returns true if the template has valid values - Validate() error -} - -// Template is file builder based on a file template -type Template interface { - Builder - // GetBody returns the template body - GetBody() string - // SetTemplateDefaults sets the default values for templates - SetTemplateDefaults() error -} - -// Inserter is a file builder that inserts code fragments in marked positions -type Inserter interface { - Builder - // GetMarkers returns the different markers where code fragments will be inserted - GetMarkers() []Marker - // GetCodeFragments returns a map that binds markers to code fragments - GetCodeFragments() CodeFragmentsMap -} - -// HasDomain allows the domain to be used on a template -type HasDomain interface { - // InjectDomain sets the template domain - InjectDomain(string) -} - -// HasRepository allows the repository to be used on a template -type HasRepository interface { - // InjectRepository sets the template repository - InjectRepository(string) -} - -// HasProjectName allows a project name to be used on a template. -type HasProjectName interface { - // InjectProjectName sets the template project name. - InjectProjectName(string) -} - -// HasMultiGroup allows the multi-group flag to be used on a template -type HasMultiGroup interface { - // InjectMultiGroup sets the template multi-group flag - InjectMultiGroup(bool) -} - -// HasComponentConfig allows the component-config flag to be used on a template -type HasComponentConfig interface { - // InjectComponentConfig sets the template component-config flag - InjectComponentConfig(bool) -} - -// HasBoilerplate allows a boilerplate to be used on a template -type HasBoilerplate interface { - // InjectBoilerplate sets the template boilerplate - InjectBoilerplate(string) -} - -// HasResource allows a resource to be used on a template -type HasResource interface { - // InjectResource sets the template resource - InjectResource(*resource.Resource) -} - -// UseCustomFuncMap allows a template to use a custom template.FuncMap instead of the default FuncMap. -type UseCustomFuncMap interface { - // GetFuncMap returns a custom FuncMap. - GetFuncMap() template.FuncMap -} diff --git a/pkg/plugins/internal/kubebuilder/machinery/machinery_suite_test.go b/pkg/plugins/internal/kubebuilder/machinery/machinery_suite_test.go deleted file mode 100644 index becbe2ed..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/machinery_suite_test.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "testing" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -func TestMachinery(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Machinery suite") -} diff --git a/pkg/plugins/internal/kubebuilder/machinery/marker.go b/pkg/plugins/internal/kubebuilder/machinery/marker.go deleted file mode 100644 index e048615c..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/marker.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "fmt" - "path/filepath" - "strings" -) - -const prefix = "+kubebuilder:scaffold:" - -var commentsByExt = map[string]string{ - ".go": "//", - ".yaml": "#", - ".yml": "#", - // When adding additional file extensions, update also the NewMarkerFor documentation and error -} - -// Marker represents a machine-readable comment that will be used for scaffolding purposes -type Marker struct { - comment string - value string -} - -// NewMarkerFor creates a new marker customized for the specific file -// Supported file extensions: .go, .yaml, .yml -func NewMarkerFor(path string, value string) Marker { - ext := filepath.Ext(path) - if comment, found := commentsByExt[ext]; found { - return Marker{comment, value} - } - - extensions := make([]string, 0, len(commentsByExt)) - for extension := range commentsByExt { - extensions = append(extensions, fmt.Sprintf("%q", extension)) - } - panic(fmt.Errorf("unknown file extension: '%s', expected one of: %s", ext, strings.Join(extensions, ", "))) -} - -// String implements Stringer -func (m Marker) String() string { - return m.comment + prefix + m.value -} - -// EqualsLine compares a marker with a string representation to check if they are the same marker -func (m Marker) EqualsLine(line string) bool { - line = strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), m.comment)) - return line == prefix+m.value -} - -// CodeFragments represents a set of code fragments -// A code fragment is a piece of code provided as a Go string, it may have multiple lines -type CodeFragments []string - -// CodeFragmentsMap binds Markers and CodeFragments together -type CodeFragmentsMap map[Marker]CodeFragments diff --git a/pkg/plugins/internal/kubebuilder/machinery/marker_test.go b/pkg/plugins/internal/kubebuilder/machinery/marker_test.go deleted file mode 100644 index 2f4468ff..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/marker_test.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" -) - -var _ = Describe("NerMarkerFor", func() { - DescribeTable("should create valid markers for known extensions", - func(path, comment string) { Expect(NewMarkerFor(path, "").comment).To(Equal(comment)) }, - Entry("for go files", "file.go", "//"), - Entry("for yaml files", "file.yaml", "#"), - Entry("for yaml files (short version)", "file.yml", "#"), - ) - - It("should panic for unknown extensions", func() { - // testing panics require to use a function with no arguments - Expect(func() { NewMarkerFor("file.unkownext", "") }).To(Panic()) - }) -}) - -var _ = Describe("Marker", func() { - Context("String", func() { - DescribeTable("should return the right string representation", - func(marker Marker, str string) { Expect(marker.String()).To(Equal(str)) }, - Entry("for go files", Marker{comment: "//", value: "test"}, "//+kubebuilder:scaffold:test"), - Entry("for yaml files", Marker{comment: "#", value: "test"}, "#+kubebuilder:scaffold:test"), - ) - }) -}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/mixins.go b/pkg/plugins/internal/kubebuilder/machinery/mixins.go deleted file mode 100644 index 9529e687..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/mixins.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -// PathMixin provides file builders with a path field -type PathMixin struct { - // Path is the of the file - Path string -} - -// GetPath implements Builder -func (t *PathMixin) GetPath() string { - return t.Path -} - -// IfExistsActionMixin provides file builders with a if-exists-action field -type IfExistsActionMixin struct { - // IfExistsAction determines what to do if the file exists - IfExistsAction IfExistsAction -} - -// GetIfExistsAction implements Builder -func (t *IfExistsActionMixin) GetIfExistsAction() IfExistsAction { - return t.IfExistsAction -} - -// TemplateMixin is the mixin that should be embedded in Template builders -type TemplateMixin struct { - PathMixin - IfExistsActionMixin - - // TemplateBody is the template body to execute - TemplateBody string -} - -// GetBody implements Template -func (t *TemplateMixin) GetBody() string { - return t.TemplateBody -} - -// InserterMixin is the mixin that should be embedded in Inserter builders -type InserterMixin struct { - PathMixin -} - -// GetIfExistsAction implements Builder -func (t *InserterMixin) GetIfExistsAction() IfExistsAction { - // Inserter builders always need to overwrite previous files - return OverwriteFile -} - -// DomainMixin provides templates with a injectable domain field -type DomainMixin struct { - // Domain is the domain for the APIs - Domain string -} - -// InjectDomain implements HasDomain -func (m *DomainMixin) InjectDomain(domain string) { - if m.Domain == "" { - m.Domain = domain - } -} - -// RepositoryMixin provides templates with a injectable repository field -type RepositoryMixin struct { - // Repo is the go project package path - Repo string -} - -// InjectRepository implements HasRepository -func (m *RepositoryMixin) InjectRepository(repository string) { - if m.Repo == "" { - m.Repo = repository - } -} - -// ProjectNameMixin provides templates with an injectable project name field. -type ProjectNameMixin struct { - ProjectName string -} - -// InjectProjectName implements HasProjectName. -func (m *ProjectNameMixin) InjectProjectName(projectName string) { - if m.ProjectName == "" { - m.ProjectName = projectName - } -} - -// MultiGroupMixin provides templates with a injectable multi-group flag field -type MultiGroupMixin struct { - // MultiGroup is the multi-group flag - MultiGroup bool -} - -// InjectMultiGroup implements HasMultiGroup -func (m *MultiGroupMixin) InjectMultiGroup(flag bool) { - m.MultiGroup = flag -} - -// ComponentConfigMixin provides templates with a injectable component-config flag field -type ComponentConfigMixin struct { - // ComponentConfig is the component-config flag - ComponentConfig bool -} - -// InjectComponentConfig implements HasComponentConfig -func (m *ComponentConfigMixin) InjectComponentConfig(flag bool) { - m.ComponentConfig = flag -} - -// BoilerplateMixin provides templates with a injectable boilerplate field -type BoilerplateMixin struct { - // Boilerplate is the contents of a Boilerplate go header file - Boilerplate string -} - -// InjectBoilerplate implements HasBoilerplate -func (m *BoilerplateMixin) InjectBoilerplate(boilerplate string) { - if m.Boilerplate == "" { - m.Boilerplate = boilerplate - } -} - -// ResourceMixin provides templates with a injectable resource field -type ResourceMixin struct { - Resource *resource.Resource -} - -// InjectResource implements HasResource -func (m *ResourceMixin) InjectResource(res *resource.Resource) { - if m.Resource == nil { - m.Resource = res - } -} diff --git a/pkg/plugins/internal/kubebuilder/machinery/mixins_test.go b/pkg/plugins/internal/kubebuilder/machinery/mixins_test.go deleted file mode 100644 index 944e4c0c..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/mixins_test.go +++ /dev/null @@ -1,188 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -type mockTemplate struct { - TemplateMixin - DomainMixin - RepositoryMixin - ProjectNameMixin - MultiGroupMixin - ComponentConfigMixin - BoilerplateMixin - ResourceMixin -} - -type mockInserter struct { - // InserterMixin requires a different type because it collides with TemplateMixin - InserterMixin -} - -var _ = Describe("TemplateMixin", func() { - const ( - path = "path/to/file.go" - ifExistsAction = SkipFile - body = "content" - ) - - var tmp = mockTemplate{ - TemplateMixin: TemplateMixin{ - PathMixin: PathMixin{path}, - IfExistsActionMixin: IfExistsActionMixin{ifExistsAction}, - TemplateBody: body, - }, - } - - Context("GetPath", func() { - It("should return the path", func() { - Expect(tmp.GetPath()).To(Equal(path)) - }) - }) - - Context("GetIfExistsAction", func() { - It("should return the if-exists action", func() { - Expect(tmp.GetIfExistsAction()).To(Equal(ifExistsAction)) - }) - }) - - Context("GetBody", func() { - It("should return the body", func() { - Expect(tmp.GetBody()).To(Equal(body)) - }) - }) -}) - -var _ = Describe("InserterMixin", func() { - const path = "path/to/file.go" - - var tmp = mockInserter{ - InserterMixin: InserterMixin{ - PathMixin: PathMixin{path}, - }, - } - - Context("GetPath", func() { - It("should return the path", func() { - Expect(tmp.GetPath()).To(Equal(path)) - }) - }) - - Context("GetIfExistsAction", func() { - It("should return overwrite file always", func() { - Expect(tmp.GetIfExistsAction()).To(Equal(OverwriteFile)) - }) - }) -}) - -var _ = Describe("DomainMixin", func() { - const domain = "my.domain" - - var tmp = mockTemplate{} - - Context("InjectDomain", func() { - It("should inject the provided domain", func() { - tmp.InjectDomain(domain) - Expect(tmp.Domain).To(Equal(domain)) - }) - }) -}) - -var _ = Describe("RepositoryMixin", func() { - const repo = "test" - - var tmp = mockTemplate{} - - Context("InjectRepository", func() { - It("should inject the provided repository", func() { - tmp.InjectRepository(repo) - Expect(tmp.Repo).To(Equal(repo)) - }) - }) -}) - -var _ = Describe("ProjectNameMixin", func() { - const name = "my project" - - var tmp = mockTemplate{} - - Context("InjectProjectName", func() { - It("should inject the provided project name", func() { - tmp.InjectProjectName(name) - Expect(tmp.ProjectName).To(Equal(name)) - }) - }) -}) - -var _ = Describe("MultiGroupMixin", func() { - var tmp = mockTemplate{} - - Context("InjectMultiGroup", func() { - It("should inject the provided multi group flag", func() { - tmp.InjectMultiGroup(true) - Expect(tmp.MultiGroup).To(BeTrue()) - }) - }) -}) - -var _ = Describe("ComponentConfigMixin", func() { - var tmp = mockTemplate{} - - Context("InjectComponentConfig", func() { - It("should inject the provided component config flag", func() { - tmp.InjectComponentConfig(true) - Expect(tmp.ComponentConfig).To(BeTrue()) - }) - }) -}) - -var _ = Describe("BoilerplateMixin", func() { - const boilerplate = "Copyright" - - var tmp = mockTemplate{} - - Context("InjectBoilerplate", func() { - It("should inject the provided boilerplate", func() { - tmp.InjectBoilerplate(boilerplate) - Expect(tmp.Boilerplate).To(Equal(boilerplate)) - }) - }) -}) - -var _ = Describe("ResourceMixin", func() { - var res = &resource.Resource{GVK: resource.GVK{ - Group: "group", - Domain: "my.domain", - Version: "v1", - Kind: "Kind", - }} - - var tmp = mockTemplate{} - - Context("InjectResource", func() { - It("should inject the provided resource", func() { - tmp.InjectResource(res) - Expect(tmp.Resource.GVK.IsEqualTo(res.GVK)).To(BeTrue()) - }) - }) -}) diff --git a/pkg/plugins/internal/kubebuilder/machinery/scaffold.go b/pkg/plugins/internal/kubebuilder/machinery/scaffold.go deleted file mode 100644 index 3b79104a..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/scaffold.go +++ /dev/null @@ -1,443 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "bufio" - "bytes" - "fmt" - "os" - "path/filepath" - "strings" - "text/template" - - "github.com/spf13/afero" - "golang.org/x/tools/imports" - - "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -const ( - createOrUpdate = os.O_WRONLY | os.O_CREATE | os.O_TRUNC - - defaultDirectoryPermission os.FileMode = 0700 - defaultFilePermission os.FileMode = 0600 -) - -var options = imports.Options{ - Comments: true, - TabIndent: true, - TabWidth: 8, - FormatOnly: true, -} - -// Scaffold uses templates to scaffold new files -type Scaffold struct { - // fs allows to mock the file system for tests - fs afero.Fs - - // permissions for new directories and files - dirPerm os.FileMode - filePerm os.FileMode - - // injector is used to provide several fields to the templates - injector injector -} - -// ScaffoldOption allows to provide optional arguments to the Scaffold -type ScaffoldOption func(*Scaffold) - -// NewScaffold returns a new Scaffold with the provided plugins -func NewScaffold(fs Filesystem, options ...ScaffoldOption) *Scaffold { - s := &Scaffold{ - fs: fs.FS, - dirPerm: defaultDirectoryPermission, - filePerm: defaultFilePermission, - } - - for _, option := range options { - option(s) - } - - return s -} - -// WithDirectoryPermissions sets the permissions for new directories -func WithDirectoryPermissions(dirPerm os.FileMode) ScaffoldOption { - return func(s *Scaffold) { - s.dirPerm = dirPerm - } -} - -// WithFilePermissions sets the permissions for new files -func WithFilePermissions(filePerm os.FileMode) ScaffoldOption { - return func(s *Scaffold) { - s.filePerm = filePerm - } -} - -// WithConfig provides the project configuration to the Scaffold -func WithConfig(cfg config.Config) ScaffoldOption { - return func(s *Scaffold) { - s.injector.config = cfg - - if cfg != nil && cfg.GetRepository() != "" { - imports.LocalPrefix = cfg.GetRepository() - } - } -} - -// WithBoilerplate provides the boilerplate to the Scaffold -func WithBoilerplate(boilerplate string) ScaffoldOption { - return func(s *Scaffold) { - s.injector.boilerplate = boilerplate - } -} - -// WithResource provides the resource to the Scaffold -func WithResource(resource *resource.Resource) ScaffoldOption { - return func(s *Scaffold) { - s.injector.resource = resource - } -} - -// Execute writes to disk the provided files -func (s *Scaffold) Execute(builders ...Builder) error { - // Initialize the files - files := make(map[string]*File, len(builders)) - - for _, builder := range builders { - // Inject common fields - s.injector.injectInto(builder) - - // Validate file builders - if reqValBuilder, requiresValidation := builder.(RequiresValidation); requiresValidation { - if err := reqValBuilder.Validate(); err != nil { - return ValidateError{err} - } - } - - // Build models for Template builders - if t, isTemplate := builder.(Template); isTemplate { - if err := s.buildFileModel(t, files); err != nil { - return err - } - } - - // Build models for Inserter builders - if i, isInserter := builder.(Inserter); isInserter { - if err := s.updateFileModel(i, files); err != nil { - return err - } - } - } - - // Persist the files to disk - for _, f := range files { - if err := s.writeFile(f); err != nil { - return err - } - } - - return nil -} - -// buildFileModel scaffolds a single file -func (Scaffold) buildFileModel(t Template, models map[string]*File) error { - // Set the template default values - if err := t.SetTemplateDefaults(); err != nil { - return SetTemplateDefaultsError{err} - } - - path := t.GetPath() - - // Handle already existing models - if _, found := models[path]; found { - switch t.GetIfExistsAction() { - case SkipFile: - return nil - case Error: - return ModelAlreadyExistsError{path} - case OverwriteFile: - default: - return UnknownIfExistsActionError{path, t.GetIfExistsAction()} - } - } - - b, err := doTemplate(t) - if err != nil { - return err - } - - models[path] = &File{ - Path: path, - Contents: string(b), - IfExistsAction: t.GetIfExistsAction(), - } - return nil -} - -// doTemplate executes the template for a file using the input -func doTemplate(t Template) ([]byte, error) { - // Create a new template.Template using the type of the Template as the name - temp := template.New(fmt.Sprintf("%T", t)) - - // Set the function map to be used - fm := DefaultFuncMap() - if templateWithFuncMap, hasCustomFuncMap := t.(UseCustomFuncMap); hasCustomFuncMap { - fm = templateWithFuncMap.GetFuncMap() - } - temp.Funcs(fm) - - // Set the template body - if _, err := temp.Parse(t.GetBody()); err != nil { - return nil, err - } - - // Execute the template - out := &bytes.Buffer{} - if err := temp.Execute(out, t); err != nil { - return nil, err - } - b := out.Bytes() - - // TODO(adirio): move go-formatting to write step - // gofmt the imports - if filepath.Ext(t.GetPath()) == ".go" { - var err error - if b, err = imports.Process(t.GetPath(), b, &options); err != nil { - return nil, err - } - } - - return b, nil -} - -// updateFileModel updates a single file -func (s Scaffold) updateFileModel(i Inserter, models map[string]*File) error { - m, err := s.loadPreviousModel(i, models) - if err != nil { - return err - } - - // Get valid code fragments - codeFragments := getValidCodeFragments(i) - - // Remove code fragments that already were applied - err = filterExistingValues(m.Contents, codeFragments) - if err != nil { - return err - } - - // If no code fragment to insert, we are done - if len(codeFragments) == 0 { - return nil - } - - content, err := insertStrings(m.Contents, codeFragments) - if err != nil { - return err - } - - // TODO(adirio): move go-formatting to write step - formattedContent := content - if ext := filepath.Ext(i.GetPath()); ext == ".go" { - formattedContent, err = imports.Process(i.GetPath(), content, nil) - if err != nil { - return err - } - } - - m.Contents = string(formattedContent) - m.IfExistsAction = OverwriteFile - models[m.Path] = m - return nil -} - -// loadPreviousModel gets the previous model from the models map or the actual file -func (s Scaffold) loadPreviousModel(i Inserter, models map[string]*File) (*File, error) { - path := i.GetPath() - - // Lets see if we already have a model for this file - if m, found := models[path]; found { - // Check if there is already an scaffolded file - exists, err := afero.Exists(s.fs, path) - if err != nil { - return nil, ExistsFileError{err} - } - - // If there is a model but no scaffolded file we return the model - if !exists { - return m, nil - } - - // If both a model and a file are found, check which has preference - switch m.IfExistsAction { - case SkipFile: - // File has preference - fromFile, err := s.loadModelFromFile(path) - if err != nil { - return m, nil - } - return fromFile, nil - case Error: - // Writing will result in an error, so we can return error now - return nil, FileAlreadyExistsError{path} - case OverwriteFile: - // Model has preference - return m, nil - default: - return nil, UnknownIfExistsActionError{path, m.IfExistsAction} - } - } - - // There was no model - return s.loadModelFromFile(path) -} - -// loadModelFromFile gets the previous model from the actual file -func (s Scaffold) loadModelFromFile(path string) (f *File, err error) { - reader, err := s.fs.Open(path) - if err != nil { - return nil, OpenFileError{err} - } - defer func() { - if closeErr := reader.Close(); err == nil && closeErr != nil { - err = CloseFileError{closeErr} - } - }() - - content, err := afero.ReadAll(reader) - if err != nil { - return nil, ReadFileError{err} - } - - return &File{Path: path, Contents: string(content)}, nil -} - -// getValidCodeFragments obtains the code fragments from a file.Inserter -func getValidCodeFragments(i Inserter) CodeFragmentsMap { - // Get the code fragments - codeFragments := i.GetCodeFragments() - - // Validate the code fragments - validMarkers := i.GetMarkers() - for marker := range codeFragments { - valid := false - for _, validMarker := range validMarkers { - if marker == validMarker { - valid = true - break - } - } - if !valid { - delete(codeFragments, marker) - } - } - - return codeFragments -} - -// filterExistingValues removes the single-line values that already exists -// TODO: Add support for multi-line duplicate values -func filterExistingValues(content string, codeFragmentsMap CodeFragmentsMap) error { - scanner := bufio.NewScanner(strings.NewReader(content)) - for scanner.Scan() { - line := scanner.Text() - for marker, codeFragments := range codeFragmentsMap { - for i, codeFragment := range codeFragments { - if strings.TrimSpace(line) == strings.TrimSpace(codeFragment) { - codeFragmentsMap[marker] = append(codeFragments[:i], codeFragments[i+1:]...) - } - } - if len(codeFragmentsMap[marker]) == 0 { - delete(codeFragmentsMap, marker) - } - } - } - if err := scanner.Err(); err != nil { - return err - } - return nil -} - -func insertStrings(content string, codeFragmentsMap CodeFragmentsMap) ([]byte, error) { - out := new(bytes.Buffer) - - scanner := bufio.NewScanner(strings.NewReader(content)) - for scanner.Scan() { - line := scanner.Text() - - for marker, codeFragments := range codeFragmentsMap { - if marker.EqualsLine(line) { - for _, codeFragment := range codeFragments { - _, _ = out.WriteString(codeFragment) // bytes.Buffer.WriteString always returns nil errors - } - } - } - - _, _ = out.WriteString(line + "\n") // bytes.Buffer.WriteString always returns nil errors - } - if err := scanner.Err(); err != nil { - return nil, err - } - - return out.Bytes(), nil -} - -func (s Scaffold) writeFile(f *File) (err error) { - // Check if the file to write already exists - exists, err := afero.Exists(s.fs, f.Path) - if err != nil { - return ExistsFileError{err} - } - if exists { - switch f.IfExistsAction { - case OverwriteFile: - // By not returning, the file is written as if it didn't exist - case SkipFile: - // By returning nil, the file is not written but the process will carry on - return nil - case Error: - // By returning an error, the file is not written and the process will fail - return FileAlreadyExistsError{f.Path} - } - } - - // Create the directory if needed - if err := s.fs.MkdirAll(filepath.Dir(f.Path), s.dirPerm); err != nil { - return CreateDirectoryError{err} - } - - // Create or truncate the file - writer, err := s.fs.OpenFile(f.Path, createOrUpdate, s.filePerm) - if err != nil { - return CreateFileError{err} - } - defer func() { - if closeErr := writer.Close(); err == nil && closeErr != nil { - err = CloseFileError{err} - } - }() - - if _, err := writer.Write([]byte(f.Contents)); err != nil { - return WriteFileError{err} - } - - return nil -} diff --git a/pkg/plugins/internal/kubebuilder/machinery/scaffold_test.go b/pkg/plugins/internal/kubebuilder/machinery/scaffold_test.go deleted file mode 100644 index 96142e25..00000000 --- a/pkg/plugins/internal/kubebuilder/machinery/scaffold_test.go +++ /dev/null @@ -1,505 +0,0 @@ -/* -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package machinery - -import ( - "errors" - "os" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - "github.com/spf13/afero" - - cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -var _ = Describe("Scaffold", func() { - Describe("NewScaffold", func() { - It("should succeed for no option", func() { - s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}) - Expect(s.fs).NotTo(BeNil()) - Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) - Expect(s.filePerm).To(Equal(defaultFilePermission)) - Expect(s.injector.config).To(BeNil()) - Expect(s.injector.boilerplate).To(Equal("")) - Expect(s.injector.resource).To(BeNil()) - }) - - It("should succeed with directory permissions option", func() { - const dirPermissions os.FileMode = 0755 - - s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithDirectoryPermissions(dirPermissions)) - Expect(s.fs).NotTo(BeNil()) - Expect(s.dirPerm).To(Equal(dirPermissions)) - Expect(s.filePerm).To(Equal(defaultFilePermission)) - Expect(s.injector.config).To(BeNil()) - Expect(s.injector.boilerplate).To(Equal("")) - Expect(s.injector.resource).To(BeNil()) - }) - - It("should succeed with file permissions option", func() { - const filePermissions os.FileMode = 0755 - - s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithFilePermissions(filePermissions)) - Expect(s.fs).NotTo(BeNil()) - Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) - Expect(s.filePerm).To(Equal(filePermissions)) - Expect(s.injector.config).To(BeNil()) - Expect(s.injector.boilerplate).To(Equal("")) - Expect(s.injector.resource).To(BeNil()) - }) - - It("should succeed with config option", func() { - cfg := cfgv3.New() - - s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithConfig(cfg)) - Expect(s.fs).NotTo(BeNil()) - Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) - Expect(s.filePerm).To(Equal(defaultFilePermission)) - Expect(s.injector.config).NotTo(BeNil()) - Expect(s.injector.config.GetVersion().Compare(cfgv3.Version)).To(Equal(0)) - Expect(s.injector.boilerplate).To(Equal("")) - Expect(s.injector.resource).To(BeNil()) - }) - - It("should succeed with boilerplate option", func() { - const boilerplate = "Copyright" - - s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithBoilerplate(boilerplate)) - Expect(s.fs).NotTo(BeNil()) - Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) - Expect(s.filePerm).To(Equal(defaultFilePermission)) - Expect(s.injector.config).To(BeNil()) - Expect(s.injector.boilerplate).To(Equal(boilerplate)) - Expect(s.injector.resource).To(BeNil()) - }) - - It("should succeed with resource option", func() { - var res = &resource.Resource{GVK: resource.GVK{ - Group: "group", - Domain: "my.domain", - Version: "v1", - Kind: "Kind", - }} - - s := NewScaffold(Filesystem{FS: afero.NewMemMapFs()}, WithResource(res)) - Expect(s.fs).NotTo(BeNil()) - Expect(s.dirPerm).To(Equal(defaultDirectoryPermission)) - Expect(s.filePerm).To(Equal(defaultFilePermission)) - Expect(s.injector.config).To(BeNil()) - Expect(s.injector.boilerplate).To(Equal("")) - Expect(s.injector.resource).NotTo(BeNil()) - Expect(s.injector.resource.GVK.IsEqualTo(res.GVK)).To(BeTrue()) - }) - }) - - Describe("Scaffold.Execute", func() { - const ( - path = "filename" - pathGo = path + ".go" - pathYaml = path + ".yaml" - content = "Hello world!" - ) - - var ( - testErr = errors.New("error text") - - s *Scaffold - ) - - BeforeEach(func() { - s = &Scaffold{fs: afero.NewMemMapFs()} - }) - - DescribeTable("successes", - func(path, expected string, files ...Builder) { - Expect(s.Execute(files...)).To(Succeed()) - - b, err := afero.ReadFile(s.fs, path) - Expect(err).NotTo(HaveOccurred()) - Expect(string(b)).To(Equal(expected)) - }, - Entry("should write the file", - path, content, - fakeTemplate{fakeBuilder: fakeBuilder{path: path}, body: content}, - ), - Entry("should skip optional models if already have one", - path, content, - fakeTemplate{fakeBuilder: fakeBuilder{path: path}, body: content}, - fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, - ), - Entry("should overwrite required models if already have one", - path, content, - fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, - fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: OverwriteFile}, body: content}, - ), - Entry("should format a go file", - pathGo, "package file\n", - fakeTemplate{fakeBuilder: fakeBuilder{path: pathGo}, body: "package file"}, - ), - ) - - DescribeTable("file builders related errors", - func(errType interface{}, files ...Builder) { - err := s.Execute(files...) - Expect(err).To(HaveOccurred()) - Expect(errors.As(err, errType)).To(BeTrue()) - }, - Entry("should fail if unable to validate a file builder", - &ValidateError{}, - fakeRequiresValidation{validateErr: testErr}, - ), - Entry("should fail if unable to set default values for a template", - &SetTemplateDefaultsError{}, - fakeTemplate{err: testErr}, - ), - Entry("should fail if an unexpected previous model is found", - &ModelAlreadyExistsError{}, - fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, - fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: Error}}, - ), - Entry("should fail if behavior if-exists-action is not defined", - &UnknownIfExistsActionError{}, - fakeTemplate{fakeBuilder: fakeBuilder{path: path}}, - fakeTemplate{fakeBuilder: fakeBuilder{path: path, ifExistsAction: -1}}, - ), - ) - - // Following errors are unwrapped, so we need to check for substrings - DescribeTable("template related errors", - func(errMsg string, files ...Builder) { - err := s.Execute(files...) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(errMsg)) - }, - Entry("should fail if a template is broken", - "template: ", - fakeTemplate{body: "{{ .Field }"}, - ), - Entry("should fail if a template params aren't provided", - "template: ", - fakeTemplate{body: "{{ .Field }}"}, - ), - Entry("should fail if unable to format a go file", - "expected 'package', found ", - fakeTemplate{fakeBuilder: fakeBuilder{path: pathGo}, body: content}, - ), - ) - - DescribeTable("insert strings", - func(path, input, expected string, files ...Builder) { - Expect(afero.WriteFile(s.fs, path, []byte(input), 0666)).To(Succeed()) - - Expect(s.Execute(files...)).To(Succeed()) - - b, err := afero.ReadFile(s.fs, path) - Expect(err).NotTo(HaveOccurred()) - Expect(string(b)).To(Equal(expected)) - }, - Entry("should insert lines for go files", - pathGo, - `package test - -//+kubebuilder:scaffold:- -`, - `package test - -var a int -var b int - -//+kubebuilder:scaffold:- -`, - fakeInserter{ - fakeBuilder: fakeBuilder{path: pathGo}, - codeFragments: CodeFragmentsMap{ - NewMarkerFor(pathGo, "-"): {"var a int\n", "var b int\n"}, - }, - }, - ), - Entry("should insert lines for yaml files", - pathYaml, - ` -#+kubebuilder:scaffold:- -`, - ` -1 -2 -#+kubebuilder:scaffold:- -`, - fakeInserter{ - fakeBuilder: fakeBuilder{path: pathYaml}, - codeFragments: CodeFragmentsMap{ - NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, - }, - }, - ), - Entry("should use models if there is no file", - pathYaml, - "", - ` -1 -2 -#+kubebuilder:scaffold:- -`, - fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml, ifExistsAction: OverwriteFile}, body: ` -#+kubebuilder:scaffold:- -`}, - fakeInserter{ - fakeBuilder: fakeBuilder{path: pathYaml}, - codeFragments: CodeFragmentsMap{ - NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, - }, - }, - ), - Entry("should use required models over files", - pathYaml, - content, - ` -1 -2 -#+kubebuilder:scaffold:- -`, - fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml, ifExistsAction: OverwriteFile}, body: ` -#+kubebuilder:scaffold:- -`}, - fakeInserter{ - fakeBuilder: fakeBuilder{path: pathYaml}, - codeFragments: CodeFragmentsMap{ - NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, - }, - }, - ), - Entry("should use files over optional models", - pathYaml, - ` -#+kubebuilder:scaffold:- -`, - ` -1 -2 -#+kubebuilder:scaffold:- -`, - fakeTemplate{fakeBuilder: fakeBuilder{path: pathYaml}, body: content}, - fakeInserter{ - fakeBuilder: fakeBuilder{path: pathYaml}, - codeFragments: CodeFragmentsMap{ - NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, - }, - }, - ), - Entry("should filter invalid markers", - pathYaml, - ` -#+kubebuilder:scaffold:- -#+kubebuilder:scaffold:* -`, - ` -1 -2 -#+kubebuilder:scaffold:- -#+kubebuilder:scaffold:* -`, - fakeInserter{ - fakeBuilder: fakeBuilder{path: pathYaml}, - markers: []Marker{NewMarkerFor(pathYaml, "-")}, - codeFragments: CodeFragmentsMap{ - NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, - NewMarkerFor(pathYaml, "*"): {"3\n", "4\n"}, - }, - }, - ), - Entry("should filter already existing one-line code fragments", - pathYaml, - ` -1 -#+kubebuilder:scaffold:- -3 -4 -#+kubebuilder:scaffold:* -`, - ` -1 -2 -#+kubebuilder:scaffold:- -3 -4 -#+kubebuilder:scaffold:* -`, - fakeInserter{ - fakeBuilder: fakeBuilder{path: pathYaml}, - codeFragments: CodeFragmentsMap{ - NewMarkerFor(pathYaml, "-"): {"1\n", "2\n"}, - NewMarkerFor(pathYaml, "*"): {"3\n", "4\n"}, - }, - }, - ), - Entry("should not insert anything if no code fragment", - pathYaml, - ` -#+kubebuilder:scaffold:- -`, - ` -#+kubebuilder:scaffold:- -`, - fakeInserter{ - fakeBuilder: fakeBuilder{path: pathYaml}, - codeFragments: CodeFragmentsMap{ - NewMarkerFor(pathYaml, "-"): {}, - }, - }, - ), - ) - - DescribeTable("insert strings related errors", - func(errType interface{}, files ...Builder) { - Expect(afero.WriteFile(s.fs, path, []byte{}, 0666)).To(Succeed()) - - err := s.Execute(files...) - Expect(err).To(HaveOccurred()) - Expect(errors.As(err, errType)).To(BeTrue()) - }, - Entry("should fail if inserting into a model that fails when a file exists and it does exist", - &FileAlreadyExistsError{}, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: Error}}, - fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, - ), - Entry("should fail if inserting into a model with unknown behavior if the file exists and it does exist", - &UnknownIfExistsActionError{}, - fakeTemplate{fakeBuilder: fakeBuilder{path: "filename", ifExistsAction: -1}}, - fakeInserter{fakeBuilder: fakeBuilder{path: "filename"}}, - ), - ) - - Context("write when the file already exists", func() { - BeforeEach(func() { - _ = afero.WriteFile(s.fs, path, []byte{}, 0666) - }) - - It("should skip the file by default", func() { - Expect(s.Execute(fakeTemplate{ - fakeBuilder: fakeBuilder{path: path}, - body: content, - })).To(Succeed()) - - b, err := afero.ReadFile(s.fs, path) - Expect(err).NotTo(HaveOccurred()) - Expect(string(b)).To(BeEmpty()) - }) - - It("should write the file if configured to do so", func() { - Expect(s.Execute(fakeTemplate{ - fakeBuilder: fakeBuilder{path: path, ifExistsAction: OverwriteFile}, - body: content, - })).To(Succeed()) - - b, err := afero.ReadFile(s.fs, path) - Expect(err).NotTo(HaveOccurred()) - Expect(string(b)).To(Equal(content)) - }) - - It("should error if configured to do so", func() { - err := s.Execute(fakeTemplate{ - fakeBuilder: fakeBuilder{path: path, ifExistsAction: Error}, - body: content, - }) - Expect(err).To(HaveOccurred()) - Expect(errors.As(err, &FileAlreadyExistsError{})).To(BeTrue()) - }) - }) - }) -}) - -var _ Builder = fakeBuilder{} - -// fakeBuilder is used to mock a Builder -type fakeBuilder struct { - path string - ifExistsAction IfExistsAction -} - -// GetPath implements Builder -func (f fakeBuilder) GetPath() string { - return f.path -} - -// GetIfExistsAction implements Builder -func (f fakeBuilder) GetIfExistsAction() IfExistsAction { - return f.ifExistsAction -} - -var _ RequiresValidation = fakeRequiresValidation{} - -// fakeRequiresValidation is used to mock a RequiresValidation in order to test Scaffold -type fakeRequiresValidation struct { - fakeBuilder - - validateErr error -} - -// Validate implements RequiresValidation -func (f fakeRequiresValidation) Validate() error { - return f.validateErr -} - -var _ Template = fakeTemplate{} - -// fakeTemplate is used to mock a File in order to test Scaffold -type fakeTemplate struct { - fakeBuilder - - body string - err error -} - -// GetBody implements Template -func (f fakeTemplate) GetBody() string { - return f.body -} - -// SetTemplateDefaults implements Template -func (f fakeTemplate) SetTemplateDefaults() error { - if f.err != nil { - return f.err - } - - return nil -} - -type fakeInserter struct { - fakeBuilder - - markers []Marker - codeFragments CodeFragmentsMap -} - -// GetMarkers implements Inserter -func (f fakeInserter) GetMarkers() []Marker { - if f.markers != nil { - return f.markers - } - - markers := make([]Marker, 0, len(f.codeFragments)) - for marker := range f.codeFragments { - markers = append(markers, marker) - } - return markers -} - -// GetCodeFragments implements Inserter -func (f fakeInserter) GetCodeFragments() CodeFragmentsMap { - return f.codeFragments -}