diff --git a/.golangci.yml b/.golangci.yml index f45362f..1d9eedc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -71,21 +71,21 @@ linters: enable: - asciicheck - bodyclose - #- cyclop - #- depguard + # - cyclop + # - depguard - dogsled - dupl - durationcheck - errcheck - errorlint - #- exhaustive - #- exhaustivestruct + # - exhaustive + # - exhaustivestruct - exportloopref - forbidigo - forcetypeassert - funlen # disable due to false positives - #- gci + # - gci - gochecknoglobals - gochecknoinits - gocognit @@ -93,10 +93,10 @@ linters: - gocritic - gocyclo - godot - - godox + # - godox - goerr113 # disable due to false positives in detecting gofmt -s - #- gofmt + # - gofmt - goheader - goimports - gomnd @@ -119,7 +119,7 @@ linters: - paralleltest - prealloc - predeclared - #- revive + # - revive - rowserrcheck - sqlclosecheck - staticcheck @@ -133,8 +133,8 @@ linters: - unused - wastedassign - whitespace - - wrapcheck - #- wsl + # - wrapcheck + # - wsl issues: # Excluding configuration per-path, per-linter, per-text and per-source @@ -144,18 +144,25 @@ issues: - gomnd - funlen - scopelint + - testpackage - path: internal/commands/*\.go linters: - gochecknoglobals - path: internal/plugins/workload/v1/scaffolds linters: - goconst + - path: internal/plugins/workload/v2/scaffolds + linters: + - goconst - path: internal/plugins/config/v1/plugin.go linters: - gocritic - path: internal/plugins/workload/v1/scaffolds/templates/readme.go linters: - gomnd + - path: internal/plugins/workload/v2/scaffolds/templates/readme.go + linters: + - gomnd # https://github.com/go-critic/go-critic/issues/926 - linters: - gocritic diff --git a/.licenserc.json b/.licenserc.json index f090ee6..8e770b9 100644 --- a/.licenserc.json +++ b/.licenserc.json @@ -1,5 +1,5 @@ { "**/*.go": [ - "// Copyright 2023 Nukleros" + "// Copyright 2024 Nukleros" ] } \ No newline at end of file diff --git a/LICENSE b/LICENSE index 4030d30..2d5b5fb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2021 VMware Tanzu Labs +Copyright (c) 2024 Nukleros +Copyright (c) 2021 VMware, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 1a95d12..2b619b2 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,8 @@ INIT_OPTS=init \ --enable-olm=true CREATE_OPTS=create api \ --workload-config .workloadConfig/workload.yaml \ - --controller \ --resource \ + --controller \ --enable-olm=true define create_path diff --git a/NOTICE b/NOTICE index 41333e2..ce7793f 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,5 @@ operator-builder +Copyright 2024 Nukleros Copyright 2021 VMware, Inc. This product is licensed to you under the MIT license (the "License"). You may @@ -7,4 +8,3 @@ not use this product except in compliance with the MIT License. This product may include a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. - diff --git a/cmd/operator-builder/main.go b/cmd/operator-builder/main.go index e101f32..48c4dac 100644 --- a/cmd/operator-builder/main.go +++ b/cmd/operator-builder/main.go @@ -1,21 +1,29 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT package main import ( + "os" + log "github.com/sirupsen/logrus" + "github.com/nukleros/operator-builder/internal/plugins/workload" "github.com/nukleros/operator-builder/pkg/cli" ) func main() { - command, err := cli.NewKubebuilderCLI() + command, err := cli.NewKubebuilderCLI(workload.FromEnv()) if err != nil { log.Fatal(err) } + if command == nil { + log.Println("skipping command execution...") + os.Exit(0) + } + if err := command.Run(); err != nil { log.Fatal(err) } diff --git a/docs/api-updates-upgrades.md b/docs/api-updates-upgrades.md index f116f34..e0f2b5e 100644 --- a/docs/api-updates-upgrades.md +++ b/docs/api-updates-upgrades.md @@ -14,7 +14,9 @@ For example, you have begun development on a brand new operator. You have generated the source code from a set of YAML manifests with markers. While testing, you discover that a field is misspelled, or that a default value should be changed, or that a new field should be added. The following instructions -describe how to overwrite an existing API to update the existing spec. +describe how to overwrite an existing API to update the existing spec. Please +note that in the below example the `--resource=true` is not necessary and is +only provided in the example for verbosity. This option is set by default. After making the necessary changes to your manifests run the following: @@ -22,7 +24,7 @@ After making the necessary changes to your manifests run the following: operator-builder create api \ --workload-config [path/to/workload/config] \ --controller=false \ - --resource \ + --resource=true \ --force ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 698999e..9812276 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -273,7 +273,9 @@ operator-builder create api \ We again provide the same workload config file. Here we also added the `--controller` and `--resource` arguments. These indicate that we want both a -new controller and new custom resource created. +new controller and new custom resource created. Please +note that in the above example both flags are not necessary and are +only provided in the example for verbosity. These options are set by default. You now have a new working Kubernetes Operator! Next, we will test it out. diff --git a/go.mod b/go.mod index 6a4a177..906fb5d 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,26 @@ module github.com/nukleros/operator-builder -go 1.22 +go 1.22.0 -toolchain go1.22.1 +toolchain go1.22.4 require ( github.com/go-playground/validator v9.31.0+incompatible github.com/nukleros/gener8s v0.1.0 - github.com/sirupsen/logrus v1.9.0 - github.com/spf13/afero v1.9.3 - github.com/spf13/cobra v1.6.1 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/afero v1.11.0 + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.4 gopkg.in/yaml.v3 v3.0.1 - k8s.io/apimachinery v0.24.2 - k8s.io/client-go v0.24.2 + k8s.io/apimachinery v0.30.0 + k8s.io/client-go v0.30.0 + + // NOTE: please ensure the internal.utils.ControllerToolsVersion matches this entry as we + // use this both in code generation as well as the generated project code. + sigs.k8s.io/controller-tools v0.15.0 sigs.k8s.io/kubebuilder/v3 v3.9.1 + sigs.k8s.io/kubebuilder/v4 v4.0.0 ) require ( @@ -23,16 +28,16 @@ require ( github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/gobuffalo/flect v1.0.0 // indirect + github.com/gobuffalo/flect v1.0.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.4.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.13 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -42,19 +47,21 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/cast v1.5.0 // indirect - golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect - golang.org/x/mod v0.7.0 // indirect - golang.org/x/net v0.5.0 // indirect - golang.org/x/sys v0.4.0 // indirect - golang.org/x/text v0.6.0 // indirect - golang.org/x/tools v0.5.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.21.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/api v0.24.2 // indirect - k8s.io/klog/v2 v2.60.1 // indirect - k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect - sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/api v0.30.0 // indirect + k8s.io/apiextensions-apiserver v0.30.0 // indirect + k8s.io/klog/v2 v2.120.1 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 522bbb5..bae7f1a 100644 --- a/go.sum +++ b/go.sum @@ -1,715 +1,197 @@ -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= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -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= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -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= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= -github.com/gobuffalo/flect v1.0.0 h1:eBFmskjXZgAOagiTXJH25Nt5sdFwNRcb8DKZsIsAUQI= -github.com/gobuffalo/flect v1.0.0/go.mod h1:l9V6xSb4BlXwsxEMj3FVEub2nkdQjWhPvD8XTTlHPQc= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -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= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nukleros/gener8s v0.1.0 h1:fgXzEZCw61a4/4KRscJcQxZlLgjHOat73lii4ZTDR6s= github.com/nukleros/gener8s v0.1.0/go.mod h1:Y8nZcDBKIO4bwjrFT/BtNZjMGNTMdTunXsdehSrZCEs= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= -github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= -github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU= +github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -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/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -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= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= -golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/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-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/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-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -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= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -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/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -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-20200227222343-706bc42d1f0d/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-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/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-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI= -k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= -k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM= -k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= -k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA= -k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= -k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= -sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= +k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= +k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= +k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= +k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= +k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= +k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= +k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-tools v0.15.0 h1:4dxdABXGDhIa68Fiwaif0vcu32xfwmgQ+w8p+5CxoAI= +sigs.k8s.io/controller-tools v0.15.0/go.mod h1:8zUSS2T8Hx0APCNRhJWbS3CAQEbIxLa07khzh7pZmXM= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kubebuilder/v3 v3.9.1 h1:9JNKRg9GzlLBYwYRx1nQlwha8+Pd9gPyat1lj7T+jZw= sigs.k8s.io/kubebuilder/v3 v3.9.1/go.mod h1:Z4boifT/XHIZTVEAIZaPTXqjhuK8Msx2iPYJy8ic6vg= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/kubebuilder/v4 v4.0.0 h1:cEuFbjyyYH3OkF/7ppiBGhzNiItFyqrjhPNU4GT86eo= +sigs.k8s.io/kubebuilder/v4 v4.0.0/go.mod h1:c2I7vEMkI9adjqjOasga3oHIwtBhCTdAw15zSZR/BK8= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/controllergen/controllergen.go b/internal/controllergen/controllergen.go new file mode 100644 index 0000000..c8a1f08 --- /dev/null +++ b/internal/controllergen/controllergen.go @@ -0,0 +1,136 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +package controllergen + +import ( + "errors" + "fmt" + + "sigs.k8s.io/controller-tools/pkg/crd" + "sigs.k8s.io/controller-tools/pkg/deepcopy" + "sigs.k8s.io/controller-tools/pkg/genall" + "sigs.k8s.io/controller-tools/pkg/markers" + "sigs.k8s.io/controller-tools/pkg/rbac" + "sigs.k8s.io/controller-tools/pkg/schemapatcher" + "sigs.k8s.io/controller-tools/pkg/webhook" +) + +var ( + ErrGeneratorsMissing = errors.New("no generators specified") + ErrGeneratorsFailed = errors.New("not all generators ran successfully") +) + +type Generator struct { + Options []string + Registry *markers.Registry +} + +// NewObjectGenerator returns a generator instance responsible for generating the DeepCopy methods. This is the +// old `make generate` and `make manifests` commands in kubebuilder. +func NewObjectGenerator(options []string) (*Generator, error) { + registry, err := newDefaultRegistry() + if err != nil { + return nil, err + } + + return &Generator{ + Options: options, + Registry: registry, + }, nil +} + +// Execute executes the generator. +func (generator *Generator) Execute() error { + // setup the runtime + rt, err := genall.FromOptions(generator.Registry, generator.Options) + if err != nil { + return fmt.Errorf("unable to setup runtime for generator, %w", err) + } + + if len(rt.Generators) == 0 { + return ErrGeneratorsMissing + } + + // execute + if hadErrs := rt.Run(); hadErrs { + // don't obscure the actual error with a bunch of usage + return ErrGeneratorsFailed + } + + return nil +} + +// WithObjectGeneratorOptions returns an array of strings representing the object generator options. This is +// equivalent to the old `make generate` command in kubebuilder. +func WithObjectGeneratorOptions(path string) []string { + return []string{ + fmt.Sprintf(`object:headerFile="%s/hack/boilerplate.go.txt"`, path), + fmt.Sprintf(`paths="%s/apis/..."`, path), + } +} + +// WithObjectGeneratorOptions returns an array of strings representing the object generator options. This is +// equivalent to the old `make manifests` command in kubebuilder. +func WithManifestGeneratorOptions(path string) []string { + return []string{ + `crd:crdVersions=v1`, + `rbac:roleName=manager-role`, + `webhook`, + fmt.Sprintf(`paths="%s/apis/..."`, path), + fmt.Sprintf(`paths="%s/controllers/..."`, path), + fmt.Sprintf(`paths="%s/internal/..."`, path), + fmt.Sprintf(`output:crd:artifacts:config=%s/config/crd/bases`, path), + fmt.Sprintf(`output:rbac:artifacts:config=%s/config/rbac`, path), + } +} + +// newDefaultRegistry returns a registry with all defaults. Logic taken from the controller-tools package. See +// https://github.com/kubernetes-sigs/controller-tools/blob/master/cmd/controller-gen/main.go. +func newDefaultRegistry() (*markers.Registry, error) { + allGenerators := map[string]genall.Generator{ + "crd": crd.Generator{}, + "rbac": rbac.Generator{}, + "object": deepcopy.Generator{}, + "webhook": webhook.Generator{}, + "schemapatch": schemapatcher.Generator{}, + } + + output := map[string]genall.OutputRule{ + "dir": genall.OutputToDirectory(""), + "artifacts": genall.OutputArtifacts{}, + } + + registry := &markers.Registry{} + + for genName, gen := range allGenerators { + // make the generator options marker itself + defn := markers.Must(markers.MakeDefinition(genName, markers.DescribesPackage, gen)) + if err := registry.Register(defn); err != nil { + return nil, fmt.Errorf("unable to make definition for generator %s for registry, %w", genName, err) + } + + // make per-generation output rule markers + for ruleName, rule := range output { + ruleMarker := markers.Must(markers.MakeDefinition(fmt.Sprintf("output:%s:%s", genName, ruleName), markers.DescribesPackage, rule)) + if err := registry.Register(ruleMarker); err != nil { + return nil, fmt.Errorf("unable to make rule for generator %s for registry, %w", ruleName, err) + } + } + } + + // make "default output" output rule markers + for ruleName, rule := range output { + ruleMarker := markers.Must(markers.MakeDefinition("output:"+ruleName, markers.DescribesPackage, rule)) + if err := registry.Register(ruleMarker); err != nil { + return nil, fmt.Errorf("unable to make default rule output for generator %s for registry, %w", ruleName, err) + } + } + + // add in the common options markers + if err := genall.RegisterOptionsMarkers(registry); err != nil { + return nil, fmt.Errorf("unable to register options for registry, %w", err) + } + + return registry, nil +} diff --git a/internal/license/license.go b/internal/license/license.go index 7fd4262..37f921a 100644 --- a/internal/license/license.go +++ b/internal/license/license.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/inspect/inspector.go b/internal/markers/inspect/inspector.go index 7617c21..26ac8fa 100644 --- a/internal/markers/inspect/inspector.go +++ b/internal/markers/inspect/inspector.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/inspect/transform.go b/internal/markers/inspect/transform.go index ab765a0..aacbb7e 100644 --- a/internal/markers/inspect/transform.go +++ b/internal/markers/inspect/transform.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/inspect/yaml.go b/internal/markers/inspect/yaml.go index 9f7856e..486c451 100644 --- a/internal/markers/inspect/yaml.go +++ b/internal/markers/inspect/yaml.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/consume.go b/internal/markers/lexer/consume.go index d172708..7a86b0f 100644 --- a/internal/markers/lexer/consume.go +++ b/internal/markers/lexer/consume.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/consume_internal_test.go b/internal/markers/lexer/consume_internal_test.go index 60e10d0..098dda8 100644 --- a/internal/markers/lexer/consume_internal_test.go +++ b/internal/markers/lexer/consume_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/discard.go b/internal/markers/lexer/discard.go index e15277b..bed6015 100644 --- a/internal/markers/lexer/discard.go +++ b/internal/markers/lexer/discard.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/emit.go b/internal/markers/lexer/emit.go index 94273eb..df568dc 100644 --- a/internal/markers/lexer/emit.go +++ b/internal/markers/lexer/emit.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/error.go b/internal/markers/lexer/error.go index 5c8b583..49df390 100644 --- a/internal/markers/lexer/error.go +++ b/internal/markers/lexer/error.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/lexeme.go b/internal/markers/lexer/lexeme.go index 1d10e8a..d830982 100644 --- a/internal/markers/lexer/lexeme.go +++ b/internal/markers/lexer/lexeme.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/lexer.go b/internal/markers/lexer/lexer.go index 5eee35e..57d1983 100644 --- a/internal/markers/lexer/lexer.go +++ b/internal/markers/lexer/lexer.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/lexer_test.go b/internal/markers/lexer/lexer_test.go index 15879dc..bce3765 100644 --- a/internal/markers/lexer/lexer_test.go +++ b/internal/markers/lexer/lexer_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/peek.go b/internal/markers/lexer/peek.go index 3af9c50..1c0fd4f 100644 --- a/internal/markers/lexer/peek.go +++ b/internal/markers/lexer/peek.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/peek_internal_test.go b/internal/markers/lexer/peek_internal_test.go index d6c77a9..475a587 100644 --- a/internal/markers/lexer/peek_internal_test.go +++ b/internal/markers/lexer/peek_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/position.go b/internal/markers/lexer/position.go index acd4ccd..82ac334 100644 --- a/internal/markers/lexer/position.go +++ b/internal/markers/lexer/position.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/stack.go b/internal/markers/lexer/stack.go index a329e3f..b73c011 100644 --- a/internal/markers/lexer/stack.go +++ b/internal/markers/lexer/stack.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/lexer/state.go b/internal/markers/lexer/state.go index cf609cb..95ca152 100644 --- a/internal/markers/lexer/state.go +++ b/internal/markers/lexer/state.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/marker/argument.go b/internal/markers/marker/argument.go index 868427a..bd43b3d 100644 --- a/internal/markers/marker/argument.go +++ b/internal/markers/marker/argument.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/marker/marker.go b/internal/markers/marker/marker.go index 4d8aaa0..ea0edc3 100644 --- a/internal/markers/marker/marker.go +++ b/internal/markers/marker/marker.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/marker/marker_internal_test.go b/internal/markers/marker/marker_internal_test.go index 6c8c26a..636bde1 100644 --- a/internal/markers/marker/marker_internal_test.go +++ b/internal/markers/marker/marker_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/marker/registry.go b/internal/markers/marker/registry.go index 5ae19a8..5c74ea7 100644 --- a/internal/markers/marker/registry.go +++ b/internal/markers/marker/registry.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/marker/utils.go b/internal/markers/marker/utils.go index 574b4df..bb2eaa1 100644 --- a/internal/markers/marker/utils.go +++ b/internal/markers/marker/utils.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/consumed.go b/internal/markers/parser/consumed.go index 450dfef..3be72c4 100644 --- a/internal/markers/parser/consumed.go +++ b/internal/markers/parser/consumed.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/definition.go b/internal/markers/parser/definition.go index 3f09d7f..a83506f 100644 --- a/internal/markers/parser/definition.go +++ b/internal/markers/parser/definition.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/emit.go b/internal/markers/parser/emit.go index b2f8d13..6b951cf 100644 --- a/internal/markers/parser/emit.go +++ b/internal/markers/parser/emit.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/error.go b/internal/markers/parser/error.go index 55a3cc5..8efc31b 100644 --- a/internal/markers/parser/error.go +++ b/internal/markers/parser/error.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/parser.go b/internal/markers/parser/parser.go index 04243b9..fcd8b68 100644 --- a/internal/markers/parser/parser.go +++ b/internal/markers/parser/parser.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/peek.go b/internal/markers/parser/peek.go index fd9615b..cda1d23 100644 --- a/internal/markers/parser/peek.go +++ b/internal/markers/parser/peek.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/position.go b/internal/markers/parser/position.go index 5df9ce9..ce4b555 100644 --- a/internal/markers/parser/position.go +++ b/internal/markers/parser/position.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/registry.go b/internal/markers/parser/registry.go index 67d08bb..8814105 100644 --- a/internal/markers/parser/registry.go +++ b/internal/markers/parser/registry.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/state.go b/internal/markers/parser/state.go index 82c1643..984ffbf 100644 --- a/internal/markers/parser/state.go +++ b/internal/markers/parser/state.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/markers/parser/unmarshal.go b/internal/markers/parser/unmarshal.go index 7755833..0b0feb9 100644 --- a/internal/markers/parser/unmarshal.go +++ b/internal/markers/parser/unmarshal.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/config/v1/api.go b/internal/plugins/config/v1/api.go index cf75cac..0a4cd3c 100644 --- a/internal/plugins/config/v1/api.go +++ b/internal/plugins/config/v1/api.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT @@ -13,6 +13,7 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "github.com/nukleros/operator-builder/internal/plugins/workload" workloadconfig "github.com/nukleros/operator-builder/internal/workload/v1/config" "github.com/nukleros/operator-builder/internal/workload/v1/kinds" ) @@ -26,8 +27,7 @@ type createAPISubcommand struct { var _ plugin.CreateAPISubcommand = &createAPISubcommand{} func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { - fs.StringVar(&p.workloadConfigPath, "workload-config", "", "path to workload config file") - fs.BoolVar(&p.enableOlm, "enable-olm", false, "enable support for OpenShift Lifecycle Manager") + workload.AddFlags(fs, &p.workloadConfigPath, &p.enableOlm) } func (p *createAPISubcommand) InjectConfig(c config.Config) error { diff --git a/internal/plugins/config/v1/init.go b/internal/plugins/config/v1/init.go index c531d3d..864fee4 100644 --- a/internal/plugins/config/v1/init.go +++ b/internal/plugins/config/v1/init.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT @@ -12,6 +12,7 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/machinery" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "github.com/nukleros/operator-builder/internal/plugins/workload" workloadconfig "github.com/nukleros/operator-builder/internal/workload/v1/config" ) @@ -24,9 +25,9 @@ type initSubcommand struct { var _ plugin.InitSubcommand = &initSubcommand{} func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { - fs.StringVar(&p.workloadConfigPath, "workload-config", "", "path to workload config file") + workload.AddFlags(fs, &p.workloadConfigPath, &p.enableOlm) + fs.StringVar(&p.controllerImage, "controller-image", "controller:latest", "controller image") - fs.BoolVar(&p.enableOlm, "enable-olm", false, "enable support for Operator Lifecycle Manager (OLM)") } func (p *initSubcommand) InjectConfig(c config.Config) error { diff --git a/internal/plugins/config/v1/plugin.go b/internal/plugins/config/v1/plugin.go index 09cfa8e..5796dfd 100644 --- a/internal/plugins/config/v1/plugin.go +++ b/internal/plugins/config/v1/plugin.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/domain.go b/internal/plugins/domain.go index 89a4308..d663cfa 100644 --- a/internal/plugins/domain.go +++ b/internal/plugins/domain.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/license/license.go b/internal/plugins/license/license.go new file mode 100644 index 0000000..3df9541 --- /dev/null +++ b/internal/plugins/license/license.go @@ -0,0 +1,12 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +package license + +import "github.com/spf13/pflag" + +// AddFlags adds a consistent set of license flags across plugin versions and commands. +func AddFlags(fs *pflag.FlagSet, projectLicensePath, sourceHeaderPath *string) { + fs.StringVar(projectLicensePath, "project-license", "", "path to project license file") + fs.StringVar(sourceHeaderPath, "source-header-license", "", "path to file with source code header license text") +} diff --git a/internal/plugins/license/v1/init.go b/internal/plugins/license/v1/init.go index f9854b0..a8a7a02 100644 --- a/internal/plugins/license/v1/init.go +++ b/internal/plugins/license/v1/init.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT @@ -13,6 +13,7 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "github.com/nukleros/operator-builder/internal/license" + licenseplugin "github.com/nukleros/operator-builder/internal/plugins/license" ) var _ plugin.InitSubcommand = &initSubcommand{} @@ -37,8 +38,7 @@ func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta * } func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { - fs.StringVar(&p.projectLicensePath, "project-license", "", "path to project license file") - fs.StringVar(&p.sourceHeaderPath, "source-header-license", "", "path to file with source code header license text") + licenseplugin.AddFlags(fs, &p.projectLicensePath, &p.sourceHeaderPath) } func (p *initSubcommand) InjectConfig(c config.Config) { diff --git a/internal/plugins/license/v1/plugin.go b/internal/plugins/license/v1/plugin.go index 00d1330..3f340bb 100644 --- a/internal/plugins/license/v1/plugin.go +++ b/internal/plugins/license/v1/plugin.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/license/v2/edit.go b/internal/plugins/license/v2/edit.go new file mode 100644 index 0000000..b8e577a --- /dev/null +++ b/internal/plugins/license/v2/edit.go @@ -0,0 +1,73 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +package v2 + +import ( + "fmt" + + "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v4/pkg/config" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + "sigs.k8s.io/kubebuilder/v4/pkg/plugin" + + "github.com/nukleros/operator-builder/internal/license" + licenseplugin "github.com/nukleros/operator-builder/internal/plugins/license" +) + +var _ plugin.EditSubcommand = &editSubcommand{} + +type editSubcommand struct { + config config.Config + + // license files + projectLicensePath string + sourceHeaderPath string +} + +func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `This command will edit the project configuration. +Features supported: + - Update the project license and license header. +` + subcmdMeta.Examples = fmt.Sprintf(` # Add a project license file from a sample on the local filesystem +%[1]s edit --project-license /path/to/sample/LICENSE + +# Add the source file header boilerplate based on a sample on the local filesystem +%[1]s edit --source-header-license /path/to/sample/source-header.txt +`, cliMeta.CommandName) +} + +func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) { + licenseplugin.AddFlags(fs, &p.projectLicensePath, &p.sourceHeaderPath) +} + +func (p *editSubcommand) InjectConfig(c config.Config) error { + p.config = c + + return nil +} + +func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error { + // project license + if p.projectLicensePath != "" { + if err := license.UpdateProjectLicense(p.projectLicensePath); err != nil { + return fmt.Errorf("unable to update project license at %s, %w", p.projectLicensePath, err) + } + } + + // source header license + if p.sourceHeaderPath != "" { + // boilerplate + if err := license.UpdateSourceHeader(p.sourceHeaderPath); err != nil { + return fmt.Errorf("unable to update source header file at %s, %w", p.sourceHeaderPath, err) + } + + // existing source code files + if err := license.UpdateExistingSourceHeader(p.sourceHeaderPath); err != nil { + return fmt.Errorf("unable to update source header file at %s, %w", p.sourceHeaderPath, err) + } + } + + return nil +} diff --git a/internal/plugins/license/v2/init.go b/internal/plugins/license/v2/init.go new file mode 100644 index 0000000..a198e7d --- /dev/null +++ b/internal/plugins/license/v2/init.go @@ -0,0 +1,65 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package v2 + +import ( + "fmt" + + "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v4/pkg/config" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + "sigs.k8s.io/kubebuilder/v4/pkg/plugin" + + "github.com/nukleros/operator-builder/internal/license" + licenseplugin "github.com/nukleros/operator-builder/internal/plugins/license" +) + +var _ plugin.InitSubcommand = &initSubcommand{} + +type initSubcommand struct { + config config.Config + + // license files + projectLicensePath string + sourceHeaderPath string +} + +func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Add a project license file and license headers to source code files +` + subcmdMeta.Examples = fmt.Sprintf(` # Add a project license file from a sample on the local filesystem + %[1]s init --project-license /path/to/sample/LICENSE + + # Add the source file header boilerplate based on a sample on the local filesystem + %[1]s init --source-header-license /path/to/sample/source-header.txt +`, cliMeta.CommandName) +} + +func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { + licenseplugin.AddFlags(fs, &p.projectLicensePath, &p.sourceHeaderPath) +} + +func (p *initSubcommand) InjectConfig(c config.Config) { + _ = c.SetPluginChain([]string{pluginKey}) + p.config = c +} + +func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { + // project license + if p.projectLicensePath != "" { + if err := license.UpdateProjectLicense(p.projectLicensePath); err != nil { + return fmt.Errorf("unable to update project license at %s, %w", p.projectLicensePath, err) + } + } + + // source header license + if p.sourceHeaderPath != "" { + if err := license.UpdateSourceHeader(p.sourceHeaderPath); err != nil { + return fmt.Errorf("unable to update source header file at %s, %w", p.sourceHeaderPath, err) + } + } + + return nil +} diff --git a/internal/plugins/license/v2/plugin.go b/internal/plugins/license/v2/plugin.go new file mode 100644 index 0000000..e667cf7 --- /dev/null +++ b/internal/plugins/license/v2/plugin.go @@ -0,0 +1,43 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package v2 + +import ( + "sigs.k8s.io/kubebuilder/v4/pkg/config" + cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v4/pkg/plugin" + + "github.com/nukleros/operator-builder/internal/plugins" +) + +const pluginName = "license." + plugins.DefaultNameQualifier + +//nolint:gochecknoglobals //needed for plugin architecture +var ( + pluginVersion = plugin.Version{Number: 2} + supportedProjectVersions = []config.Version{cfgv3.Version} + pluginKey = plugin.KeyFor(Plugin{}) +) + +var ( + _ plugin.Plugin = Plugin{} + _ plugin.Init = Plugin{} + _ plugin.Edit = Plugin{} +) + +type Plugin struct { + initSubcommand + editSubcommand +} + +func (Plugin) Name() string { return pluginName } +func (Plugin) Version() plugin.Version { return pluginVersion } +func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions } + +//nolint:gocritic // needed to implement interface +func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand } + +//nolint:gocritic // needed to implement interface +func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand } diff --git a/internal/plugins/workload/v1/api.go b/internal/plugins/workload/v1/api.go index db454b4..10482c1 100644 --- a/internal/plugins/workload/v1/api.go +++ b/internal/plugins/workload/v1/api.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/init.go b/internal/plugins/workload/v1/init.go index 5af750b..5dc4806 100644 --- a/internal/plugins/workload/v1/init.go +++ b/internal/plugins/workload/v1/init.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/plugin.go b/internal/plugins/workload/v1/plugin.go index f7f990f..618ba7f 100644 --- a/internal/plugins/workload/v1/plugin.go +++ b/internal/plugins/workload/v1/plugin.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/api.go b/internal/plugins/workload/v1/scaffolds/api.go index 24952ae..04998bb 100644 --- a/internal/plugins/workload/v1/scaffolds/api.go +++ b/internal/plugins/workload/v1/scaffolds/api.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT @@ -7,9 +7,9 @@ package scaffolds import ( "errors" "fmt" - "log" "strings" + log "github.com/sirupsen/logrus" "github.com/spf13/afero" "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" diff --git a/internal/plugins/workload/v1/scaffolds/init.go b/internal/plugins/workload/v1/scaffolds/init.go index 6ff37cf..6e21a02 100644 --- a/internal/plugins/workload/v1/scaffolds/init.go +++ b/internal/plugins/workload/v1/scaffolds/init.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT @@ -6,8 +6,8 @@ package scaffolds import ( "fmt" - "log" + log "github.com/sirupsen/logrus" "github.com/spf13/afero" "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" diff --git a/internal/plugins/workload/v1/scaffolds/templates/api/group.go b/internal/plugins/workload/v1/scaffolds/templates/api/group.go index db23f87..d070bbd 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/api/group.go +++ b/internal/plugins/workload/v1/scaffolds/templates/api/group.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/api/kind.go b/internal/plugins/workload/v1/scaffolds/templates/api/kind.go index 2166959..25555bf 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/api/kind.go +++ b/internal/plugins/workload/v1/scaffolds/templates/api/kind.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/api/resources/constants.go b/internal/plugins/workload/v1/scaffolds/templates/api/resources/constants.go index b8dd59f..c6d0df1 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/api/resources/constants.go +++ b/internal/plugins/workload/v1/scaffolds/templates/api/resources/constants.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // SPDX-License-Identifier: MIT package resources diff --git a/internal/plugins/workload/v1/scaffolds/templates/api/resources/definition.go b/internal/plugins/workload/v1/scaffolds/templates/api/resources/definition.go index 0cd69bb..e862104 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/api/resources/definition.go +++ b/internal/plugins/workload/v1/scaffolds/templates/api/resources/definition.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/api/resources/mutate.go b/internal/plugins/workload/v1/scaffolds/templates/api/resources/mutate.go index 6285057..3f688aa 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/api/resources/mutate.go +++ b/internal/plugins/workload/v1/scaffolds/templates/api/resources/mutate.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // SPDX-License-Identifier: MIT package resources diff --git a/internal/plugins/workload/v1/scaffolds/templates/api/resources/resources.go b/internal/plugins/workload/v1/scaffolds/templates/api/resources/resources.go index fadd34b..d8331ec 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/api/resources/resources.go +++ b/internal/plugins/workload/v1/scaffolds/templates/api/resources/resources.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/api/types.go b/internal/plugins/workload/v1/scaffolds/templates/api/types.go index f7f7c2a..6c3cb5a 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/api/types.go +++ b/internal/plugins/workload/v1/scaffolds/templates/api/types.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_generate.go b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_generate.go index b548daa..b1ea839 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_generate.go +++ b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_generate.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_generate_sub.go b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_generate_sub.go index c908db8..fd6e501 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_generate_sub.go +++ b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_generate_sub.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_init.go b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_init.go index dfecaf5..b605e26 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_init.go +++ b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_init.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_init_sub.go b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_init_sub.go index ab51038..af88d63 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_init_sub.go +++ b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_init_sub.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_root.go b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_root.go index 3e0a613..1dbca27 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_root.go +++ b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_root.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_version.go b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_version.go index 10523c8..ca7d5ce 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_version.go +++ b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_version.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_version_sub.go b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_version_sub.go index 261d427..3ee2d0f 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_version_sub.go +++ b/internal/plugins/workload/v1/scaffolds/templates/cli/cmd_version_sub.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/cli/main.go b/internal/plugins/workload/v1/scaffolds/templates/cli/main.go index ecc676e..64e8a65 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/cli/main.go +++ b/internal/plugins/workload/v1/scaffolds/templates/cli/main.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/config/crd/kustomization.go b/internal/plugins/workload/v1/scaffolds/templates/config/crd/kustomization.go index 5759336..9883768 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/config/crd/kustomization.go +++ b/internal/plugins/workload/v1/scaffolds/templates/config/crd/kustomization.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/config/manifests/kustomization.go b/internal/plugins/workload/v1/scaffolds/templates/config/manifests/kustomization.go index de0dd78..8c27954 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/config/manifests/kustomization.go +++ b/internal/plugins/workload/v1/scaffolds/templates/config/manifests/kustomization.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // SPDX-License-Identifier: MIT // NOTE: this was copied from operator-sdk in order to diff --git a/internal/plugins/workload/v1/scaffolds/templates/config/samples/crd_sample.go b/internal/plugins/workload/v1/scaffolds/templates/config/samples/crd_sample.go index 718ea89..9c6a40e 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/config/samples/crd_sample.go +++ b/internal/plugins/workload/v1/scaffolds/templates/config/samples/crd_sample.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/config/samples/kustomization.go b/internal/plugins/workload/v1/scaffolds/templates/config/samples/kustomization.go index 7223465..a57d890 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/config/samples/kustomization.go +++ b/internal/plugins/workload/v1/scaffolds/templates/config/samples/kustomization.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // SPDX-License-Identifier: MIT // NOTE: this was copied from operator-sdk in order to diff --git a/internal/plugins/workload/v1/scaffolds/templates/config/scorecard/scorecard.go b/internal/plugins/workload/v1/scaffolds/templates/config/scorecard/scorecard.go index d72c5cd..540f4c7 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/config/scorecard/scorecard.go +++ b/internal/plugins/workload/v1/scaffolds/templates/config/scorecard/scorecard.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // SPDX-License-Identifier: MIT // NOTE: this was copied from operator-sdk in order to diff --git a/internal/plugins/workload/v1/scaffolds/templates/controller/controller.go b/internal/plugins/workload/v1/scaffolds/templates/controller/controller.go index faf711b..24b1624 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/controller/controller.go +++ b/internal/plugins/workload/v1/scaffolds/templates/controller/controller.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/controller/controller_suitetest.go b/internal/plugins/workload/v1/scaffolds/templates/controller/controller_suitetest.go index 0a7d5aa..19b494c 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/controller/controller_suitetest.go +++ b/internal/plugins/workload/v1/scaffolds/templates/controller/controller_suitetest.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/controller/phases.go b/internal/plugins/workload/v1/scaffolds/templates/controller/phases.go index 65cb994..b7ca6a6 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/controller/phases.go +++ b/internal/plugins/workload/v1/scaffolds/templates/controller/phases.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/dockerfile.go b/internal/plugins/workload/v1/scaffolds/templates/dockerfile.go index c7da303..bdafe5d 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/dockerfile.go +++ b/internal/plugins/workload/v1/scaffolds/templates/dockerfile.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/gomod.go b/internal/plugins/workload/v1/scaffolds/templates/gomod.go index aaf5aad..06ec228 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/gomod.go +++ b/internal/plugins/workload/v1/scaffolds/templates/gomod.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT @@ -29,7 +29,7 @@ type GoMod struct { func goModDependencyMap() map[string]string { return map[string]string{ "github.com/go-logr/logr": "v1.4.1", - "github.com/nukleros/operator-builder-tools": "v0.4.0", + "github.com/nukleros/operator-builder-tools": "v0.6.1", "github.com/onsi/ginkgo/v2": "v2.17.1", "github.com/onsi/gomega": "v1.32.0", "github.com/spf13/cobra": "v1.8.0", diff --git a/internal/plugins/workload/v1/scaffolds/templates/int/dependencies/component.go b/internal/plugins/workload/v1/scaffolds/templates/int/dependencies/component.go index 5bedfb2..8d840c0 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/int/dependencies/component.go +++ b/internal/plugins/workload/v1/scaffolds/templates/int/dependencies/component.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/int/mutate/component.go b/internal/plugins/workload/v1/scaffolds/templates/int/mutate/component.go index 4c0d0e9..3d325d2 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/int/mutate/component.go +++ b/internal/plugins/workload/v1/scaffolds/templates/int/mutate/component.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/main.go b/internal/plugins/workload/v1/scaffolds/templates/main.go index c5c8692..e15f4b1 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/main.go +++ b/internal/plugins/workload/v1/scaffolds/templates/main.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/makefile.go b/internal/plugins/workload/v1/scaffolds/templates/makefile.go index b01cda9..2d57fef 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/makefile.go +++ b/internal/plugins/workload/v1/scaffolds/templates/makefile.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/readme.go b/internal/plugins/workload/v1/scaffolds/templates/readme.go index 8e95654..1bc46b5 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/readme.go +++ b/internal/plugins/workload/v1/scaffolds/templates/readme.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/test/e2e/e2e.go b/internal/plugins/workload/v1/scaffolds/templates/test/e2e/e2e.go index 6eefd10..ce6df0f 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/test/e2e/e2e.go +++ b/internal/plugins/workload/v1/scaffolds/templates/test/e2e/e2e.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v1/scaffolds/templates/test/e2e/workloads.go b/internal/plugins/workload/v1/scaffolds/templates/test/e2e/workloads.go index f3aebd4..0790e1d 100644 --- a/internal/plugins/workload/v1/scaffolds/templates/test/e2e/workloads.go +++ b/internal/plugins/workload/v1/scaffolds/templates/test/e2e/workloads.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/plugins/workload/v2/api.go b/internal/plugins/workload/v2/api.go new file mode 100644 index 0000000..f4b4ea7 --- /dev/null +++ b/internal/plugins/workload/v2/api.go @@ -0,0 +1,243 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package v2 + +import ( + "bufio" + "errors" + "fmt" + "os" + "path" + + log "github.com/sirupsen/logrus" + "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v4/pkg/config" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v4/pkg/plugin" + "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util" + goplugin "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang" + + "github.com/nukleros/operator-builder/internal/controllergen" + "github.com/nukleros/operator-builder/internal/plugins/workload" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds" + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/commands/subcommand" + workloadconfig "github.com/nukleros/operator-builder/internal/workload/v1/config" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +type createAPISubcommand struct { + config config.Config + options *goplugin.Options + resource *resource.Resource + + force bool + generateDeepCopy bool + generateManifests bool + + // Check if we have to scaffold resource and/or controller + resourceFlag *pflag.Flag + controllerFlag *pflag.Flag + + workloadConfigPath string + cliRootCommandName string + workload kinds.WorkloadBuilder + enableOlm bool +} + +var ( + ErrScaffoldCreateAPI = errors.New("unable to scaffold api") + ErrAPIResourceExists = errors.New("API resource already exists") + ErrMissingRootFile = errors.New("file should present in the root directory") +) + +var _ plugin.CreateAPISubcommand = &createAPISubcommand{} + +func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Build a new API that can capture state for workloads +` + subcmdMeta.Examples = fmt.Sprintf(` # Add API attributes defined by a workload config file + %[1]s create api --workload-config .source-manifests/workload.yaml +`, cliMeta.CommandName) +} + +func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { + workload.AddFlags(fs, &p.workloadConfigPath, &p.enableOlm) + + fs.BoolVar(&p.generateDeepCopy, "generate-deep-copy", true, + "if true, generate deep copy methods after scaffolding (equivalent of 'make generate')") + fs.BoolVar(&p.generateManifests, "generate-manifests", true, + "if true, generate manifests and crds after scaffolding (equivalent of 'make manifests')") + fs.BoolVar(&p.force, "force", false, + "attempt to create resource even if it already exists") + + p.options = &goplugin.Options{} + + fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form") + fs.BoolVar(&p.options.DoAPI, "resource", true, + "if set, generate the resource without prompting the user") + p.resourceFlag = fs.Lookup("resource") + fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced") + fs.BoolVar(&p.options.DoController, "controller", true, + "if set, generate the controller without prompting the user") + p.controllerFlag = fs.Lookup("controller") +} + +func (p *createAPISubcommand) InjectConfig(c config.Config) error { + processor, err := workloadconfig.Parse(p.workloadConfigPath) + if err != nil { + return fmt.Errorf("unable to inject config into %s, %w", p.workloadConfigPath, err) + } + + p.workload = processor.Workload + p.cliRootCommandName = p.workload.GetRootCommand().Name + + pluginConfig := workloadconfig.Plugin{ + WorkloadConfigPath: p.workloadConfigPath, + CliRootCommandName: p.cliRootCommandName, + EnableOLM: p.enableOlm, + } + + if err := c.EncodePluginConfig(workloadconfig.PluginKey, pluginConfig); err != nil { + return fmt.Errorf("unable to encode plugin config at key %s, %w", workloadconfig.PluginKey, err) + } + + p.config = c + + return nil +} + +func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { + // set from config file if not provided with command line flag + if res.Group == "" { + res.Group = p.workload.GetAPIGroup() + } + + if res.Version == "" { + res.Version = p.workload.GetAPIVersion() + } + + if res.Kind == "" { + res.Kind = p.workload.GetAPIKind() + res.Plural = resource.RegularPlural(p.workload.GetAPIKind()) + } + + // TODO: re-evaluate whether y/n input still makes sense. We should probably always + // scaffold the resource and controller. + // Ask for API and Controller if not specified + reader := bufio.NewReader(os.Stdin) + if !p.resourceFlag.Changed { + log.Println("Create Resource [y/n]") + p.options.DoAPI = util.YesNo(reader) + } + if !p.controllerFlag.Changed { + log.Println("Create Controller [y/n]") + p.options.DoController = util.YesNo(reader) + } + + p.options.UpdateResource(res, p.config) + res.Path = path.Join(p.config.GetRepository(), "apis", res.Group, res.Version) + + if err := res.Validate(); err != nil { + return err + } + + // In case we want to scaffold a resource API we need to do some checks + if p.options.DoAPI { + // Check that resource doesn't have the API scaffolded or flag force was set + if r, err := p.config.GetResource(res.GVK); err == nil && r.HasAPI() && !p.force { + return ErrAPIResourceExists + } + } + + if !p.config.HasResource(res.GVK) { + if err := p.config.AddResource(*res); err != nil { + return fmt.Errorf("unable to add resource to config, %w", err) + } + } + + p.resource = res + + return nil +} + +func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error { + processor, err := workloadconfig.Parse(p.workloadConfigPath) + if err != nil { + return fmt.Errorf("%s for %s, %w", ErrScaffoldCreateAPI.Error(), p.workloadConfigPath, err) + } + + if err := subcommand.CreateAPI(processor); err != nil { + return fmt.Errorf("%s for %s, %w", ErrScaffoldCreateAPI.Error(), p.workloadConfigPath, err) + } + + // check if main.go is present in the root directory + if _, err := os.Stat(utils.DefaultMainPath); os.IsNotExist(err) { + return fmt.Errorf("missing file [%s], %w", utils.DefaultMainPath, ErrMissingRootFile) + } + + p.workload = processor.Workload + + return nil +} + +func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewAPIScaffolder( + p.config, + p.resource, + p.workload, + p.cliRootCommandName, + p.enableOlm, + ) + scaffolder.InjectFS(fs) + + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("%s for %s, %w", ErrScaffoldInit.Error(), p.workloadConfigPath, err) + } + + return nil +} + +func (p *createAPISubcommand) PostScaffold() error { + err := util.RunCmd("Update dependencies", "go", "mod", "tidy") + if err != nil { + return err + } + + // generate deep copy functions + if p.generateDeepCopy && p.resource.HasAPI() { + log.Println("Generating DeepCopy methods and other required functions...") + + generator, err := controllergen.NewObjectGenerator(controllergen.WithObjectGeneratorOptions(".")) + if err != nil { + return fmt.Errorf("unable to create object generator, %w", err) + } + + if err := generator.Execute(); err != nil { + return fmt.Errorf("error in object generation, %w", err) + } + } else { + log.Print("Next: generate DeepCopy and other required functions with:\n$ make generate\n") + } + + // generate manifests + if p.generateManifests && p.resource.HasAPI() { + log.Println("Generating CRDs, RBAC, and controller manifests...") + + generator, err := controllergen.NewObjectGenerator(controllergen.WithManifestGeneratorOptions(".")) + if err != nil { + return fmt.Errorf("unable to create manifest generator, %w", err) + } + + if err := generator.Execute(); err != nil { + return fmt.Errorf("error in manifest generation, %w", err) + } + } else { + log.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests\n") + } + + return nil +} diff --git a/internal/plugins/workload/v2/init.go b/internal/plugins/workload/v2/init.go new file mode 100644 index 0000000..9b592d5 --- /dev/null +++ b/internal/plugins/workload/v2/init.go @@ -0,0 +1,235 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package v2 + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "unicode" + + "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v4/pkg/config" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + "sigs.k8s.io/kubebuilder/v4/pkg/plugin" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang" + + "github.com/nukleros/operator-builder/internal/plugins/workload" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds" + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/commands/subcommand" + workloadconfig "github.com/nukleros/operator-builder/internal/workload/v1/config" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +// Variables and function to check Go version requirements. +// +//nolint:gochecknoglobals +var ( + goVerMin = golang.MustParse(fmt.Sprintf("go%s", utils.GeneratedGoVersionMinimum)) + goVerMax = golang.MustParse(fmt.Sprintf("go%s", utils.GeneratedGoVersionPreferred)) +) + +var ( + ErrDirectoryNotEmpty = errors.New("target directory is not empty") +) + +type initSubcommand struct { + config config.Config + + license string + owner string + repo string + fetchDeps bool + skipGoVersionCheck bool + + workloadConfigPath string + cliRootCommandName string + controllerImage string + enableOlm bool + + workload kinds.WorkloadBuilder +} + +var ErrScaffoldInit = errors.New("unable to scaffold initial config") + +var _ plugin.InitSubcommand = &initSubcommand{} + +func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Add workload management scaffolding to a new project +` + subcmdMeta.Examples = fmt.Sprintf(` # Add project scaffolding defined by a workload config file + %[1]s init --workload-config .source-manifests/workload.yaml +`, cliMeta.CommandName) +} + +func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { + workload.AddFlags(fs, &p.workloadConfigPath, &p.enableOlm) + fs.StringVar(&p.controllerImage, "controller-image", "controller:latest", "controller image") + + fs.BoolVar(&p.skipGoVersionCheck, "skip-go-version-check", + false, "if specified, skip checking the Go version") + + // dependency args + fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "ensure dependencies are downloaded") + + // boilerplate args + fs.StringVar(&p.license, "license", "apache2", + "license to use to boilerplate, may be one of 'apache2', 'none'") + fs.StringVar(&p.owner, "owner", "", "owner to add to the copyright") + + // project args + fs.StringVar(&p.repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+ + "defaults to the go package of the current working directory.") +} + +func (p *initSubcommand) InjectConfig(c config.Config) error { + processor, err := workloadconfig.Parse(p.workloadConfigPath) + if err != nil { + return fmt.Errorf("unable to inject config into %s, %w", p.workloadConfigPath, err) + } + + p.config = c + + // operator builder always uses multi-group APIs + if err := c.SetMultiGroup(); err != nil { + return fmt.Errorf("unable to enable multigroup apis, %w", err) + } + + pluginConfig := workloadconfig.Plugin{ + WorkloadConfigPath: p.workloadConfigPath, + CliRootCommandName: processor.Workload.GetRootCommand().Name, + ControllerImg: p.controllerImage, + EnableOLM: p.enableOlm, + } + + if err := c.EncodePluginConfig(workloadconfig.PluginKey, pluginConfig); err != nil { + return fmt.Errorf("unable to encode operatorbuilder config key at %s, %w", p.workloadConfigPath, err) + } + + if err := c.SetDomain(processor.Workload.GetDomain()); err != nil { + return fmt.Errorf("unable to set project domain, %w", err) + } + + // Try to guess repository if flag is not set. + if p.repo == "" { + repoPath, err := golang.FindCurrentRepo() + if err != nil { + return fmt.Errorf("error finding current repository, %w", err) + } + p.repo = repoPath + } + + p.cliRootCommandName = pluginConfig.CliRootCommandName + + return p.config.SetRepository(p.repo) +} + +func (p *initSubcommand) PreScaffold(machinery.Filesystem) error { + processor, err := workloadconfig.Parse(p.workloadConfigPath) + if err != nil { + return fmt.Errorf("%s for %s, %w", ErrScaffoldInit.Error(), p.workloadConfigPath, err) + } + + if err := subcommand.Init(processor); err != nil { + return fmt.Errorf("%s for %s, %w", ErrScaffoldInit.Error(), p.workloadConfigPath, err) + } + + p.workload = processor.Workload + + // Ensure Go version is in the allowed range if check not turned off. + if !p.skipGoVersionCheck { + if err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil { + return err + } + } + + return checkDir() +} + +func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewInitScaffolder( + p.config, + p.workload, + p.cliRootCommandName, + p.controllerImage, + p.enableOlm, + p.license, + p.owner, + ) + scaffolder.InjectFS(fs) + + if err := scaffolder.Scaffold(); err != nil { + return fmt.Errorf("%s for %s, %w", ErrScaffoldInit.Error(), p.workloadConfigPath, err) + } + + return nil +} + +// checkDir will return error if the current directory has files which are not allowed. +// Note that, it is expected that the directory to scaffold the project is cleaned. +// Otherwise, it might face issues to do the scaffold. +func checkDir() error { + err := filepath.Walk(".", + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Allow directory trees starting with '.' + if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." { + return filepath.SkipDir + } + + // Allow files starting with '.' + if strings.HasPrefix(info.Name(), ".") { + return nil + } + + // Allow files ending with '.md' extension + if strings.HasSuffix(info.Name(), ".md") && !info.IsDir() { + return nil + } + + // Allow capitalized files except PROJECT + isCapitalized := true + for _, l := range info.Name() { + if !unicode.IsUpper(l) { + isCapitalized = false + + break + } + } + + if isCapitalized && info.Name() != "PROJECT" { + return nil + } + + // Allow files in the following list + allowedFiles := []string{ + "go.mod", // user might run `go mod init` instead of providing the `--flag` at init + "go.sum", // auto-generated file related to go.mod + } + + for _, allowedFile := range allowedFiles { + if info.Name() == allowedFile { + return nil + } + } + + // Do not allow any other file + return fmt.Errorf( + "%w, (only %s, files and directories with the prefix \".\", "+ + "files with the suffix \".md\" or capitalized files name are allowed); "+ + "found existing file %q", ErrDirectoryNotEmpty, strings.Join(allowedFiles, ", "), path) + }) + if err != nil { + return err + } + + return nil +} diff --git a/internal/plugins/workload/v2/plugin.go b/internal/plugins/workload/v2/plugin.go new file mode 100644 index 0000000..cb5fdb0 --- /dev/null +++ b/internal/plugins/workload/v2/plugin.go @@ -0,0 +1,42 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package v2 + +import ( + "sigs.k8s.io/kubebuilder/v4/pkg/config" + cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v4/pkg/plugin" + + "github.com/nukleros/operator-builder/internal/plugins" +) + +const pluginName = "workload." + plugins.DefaultNameQualifier + +//nolint:gochecknoglobals //needed for plugin architecture +var ( + pluginVersion = plugin.Version{Number: 2} + supportedProjectVersions = []config.Version{cfgv3.Version} +) + +var ( + _ plugin.Plugin = Plugin{} + _ plugin.Init = Plugin{} + _ plugin.CreateAPI = Plugin{} +) + +type Plugin struct { + initSubcommand + createAPISubcommand +} + +func (Plugin) Name() string { return pluginName } +func (Plugin) Version() plugin.Version { return pluginVersion } +func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions } + +//nolint:gocritic // needed to implement interface +func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand } + +//nolint:gocritic // needed to implement interface +func (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand } diff --git a/internal/plugins/workload/v2/scaffolds/api.go b/internal/plugins/workload/v2/scaffolds/api.go new file mode 100644 index 0000000..40755de --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/api.go @@ -0,0 +1,331 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package scaffolds + +import ( + "errors" + "fmt" + "strings" + + log "github.com/sirupsen/logrus" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v4/pkg/config" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins" + + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/api" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/api/resources" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/cli" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/config/crd" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/config/samples" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/controller" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/int/dependencies" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/int/mutate" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/test/e2e" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +const boilerplatePath = "hack/boilerplate.go.txt" + +var _ plugins.Scaffolder = &apiScaffolder{} + +var ( + ErrScaffoldWorkload = errors.New("error scaffolding workload") + ErrScaffoldMainUpdater = errors.New("error updating main.go") + ErrScaffoldCRDSample = errors.New("error scaffolding CRD sample file") + ErrScaffoldKustomization = errors.New("error scaffolding kustomization overlay") + ErrScaffoldAPITypes = errors.New("error scaffolding api types") + ErrScaffoldAPIKindInfo = errors.New("error scaffolding api kind information") + ErrScaffoldAPIResources = errors.New("error scaffolding api resource methods") + ErrScaffoldAPIChildResources = errors.New("error scaffolding api child resource definitions") + ErrScaffoldController = errors.New("error scaffolding controller logic") + ErrScaffoldE2ETest = errors.New("error scaffolding e2e tests") + ErrScaffoldCompanionCLI = errors.New("error scaffolding companion CLI") + ErrScaffoldCompanionCLIInit = errors.New("error scaffolding companion CLI init sub-command") + ErrScaffoldCompanionCLIGenerate = errors.New("error scaffolding companion CLI generate sub-command") + ErrScaffoldCompanionCLIVersion = errors.New("error scaffolding companion CLI version sub-command") + ErrScaffoldCompanionCLIRoot = errors.New("error scaffolding companion CLI root.go entrypoint") +) + +type apiScaffolder struct { + fs machinery.Filesystem + + config config.Config + resource *resource.Resource + boilerplate string + workload kinds.WorkloadBuilder + cliRootCommandName string + enableOlm bool +} + +// NewAPIScaffolder returns a new Scaffolder for project initialization operations. +func NewAPIScaffolder( + cfg config.Config, + res *resource.Resource, + workload kinds.WorkloadBuilder, + cliRootCommandName string, + enableOlm bool, +) plugins.Scaffolder { + return &apiScaffolder{ + config: cfg, + resource: res, + workload: workload, + cliRootCommandName: cliRootCommandName, + enableOlm: enableOlm, + } +} + +// InjectFS implements cmdutil.Scaffolder. +func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// scaffold implements cmdutil.Scaffolder. +func (s *apiScaffolder) Scaffold() error { + log.Println("Building API...") + + boilerplate, err := afero.ReadFile(s.fs.FS, boilerplatePath) + if err != nil { + return fmt.Errorf("unable to read boilerplate file %s, %w", boilerplatePath, err) + } + + s.boilerplate = string(boilerplate) + + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(s.boilerplate), + machinery.WithResource(s.resource), + ) + + // scaffold the workload + if err := s.scaffoldWorkload(scaffold, s.workload); err != nil { + return fmt.Errorf("%w; %s for workload type %T", err, ErrScaffoldWorkload.Error(), s.workload) + } + + return nil +} + +// scaffoldWorkload performs the execution of the scaffold for an individual workload. +// +//nolint:funlen,gocyclo +func (s *apiScaffolder) scaffoldWorkload( + scaffold *machinery.Scaffold, + workload kinds.WorkloadBuilder, +) error { + componentResource := workload.GetComponentResource( + s.config.GetDomain(), + s.config.GetRepository(), + workload.IsClusterScoped(), + ) + + // convert the component resource to a v4 resource + v4ComponentResource := &resource.Resource{ + GVK: resource.GVK(componentResource.GVK), + Plural: componentResource.Plural, + Path: componentResource.Path, + API: (*resource.API)(componentResource.API), + Controller: componentResource.Controller, + Webhooks: (*resource.Webhooks)(componentResource.Webhooks), + } + + // override the scaffold if we have a component. this will allow the Resource + // attribute of the scaffolder to be set appropriately so that things like Group, + // Version, and Kind are passed from the child component and not the parent + // workload. + if workload.IsComponent() { + scaffold = machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(s.boilerplate), + machinery.WithResource(v4ComponentResource), + ) + } + + // inject the resource as this resource so that our PROJECT file is up to date for each + // resource that we loop through + if err := s.config.UpdateResource(*v4ComponentResource); err != nil { + return fmt.Errorf("%w; error updating resource", err) + } + + doAPI := s.resource.HasAPI() + doController := s.resource.HasController() + + // scaffold the workload api. this generates files within the apis/ folder to include + // items such as common resource methods, api type definitions and child resource typed + // object definitions. + if doAPI { + if err := s.scaffoldAPI(scaffold, workload); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldAPIResources.Error()) + } + } + + // scaffold the controller. this generates the main controller logic. + if doController { + if err := scaffold.Execute( + &controller.Controller{Builder: workload}, + &controller.Phases{PackageName: workload.GetPackageName()}, + &controller.SuiteTest{}, + &dependencies.Component{}, + &mutate.Component{}, + &crd.Kustomization{}, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldController.Error()) + } + } + + // update controller main entrypoint. this updates the main.go file with logic related to + // creating the new controllers. + if err := scaffold.Execute( + &templates.MainUpdater{ + WireResource: doAPI, + WireController: doController, + }, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldMainUpdater.Error()) + } + + // scaffold the custom resource sample files. this will generate sample manifest files. + if err := scaffold.Execute( + &samples.CRDSample{ + SpecFields: workload.GetAPISpecFields(), + IsClusterScoped: workload.IsClusterScoped(), + }, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldCRDSample.Error()) + } + + // scaffold the kustomization sample if OLM is enabled. + if err := scaffold.Execute(&samples.Kustomization{}); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldKustomization.Error()) + } + + // scaffold the end-to-end tests. this will generate some common end-to-end tests for + // the controller. + if err := scaffold.Execute(&e2e.WorkloadTest{Builder: workload}); err != nil { + return fmt.Errorf("%w; %s - error updating test/e2e/%s_%s_%s_test.go", err, ErrScaffoldController.Error(), + workload.GetAPIGroup(), workload.GetAPIVersion(), strings.ToLower(workload.GetAPIKind())) + } + + // scaffold the companion CLI only if we have a root command name + if s.cliRootCommandName != "" { + if err := s.scaffoldCLI(scaffold, workload); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldCompanionCLI.Error()) + } + } + + // scaffold the components of a collection if we have a collection. this will scaffold the + // logic for a companion CLI. + if workload.IsCollection() { + for _, component := range workload.GetComponents() { + if err := s.scaffoldWorkload(scaffold, component); err != nil { + return fmt.Errorf("%w; %s for workload type %T", err, ErrScaffoldWorkload.Error(), component) + } + } + } + + return nil +} + +// scaffoldAPI runs the specific logic to scaffold anything existing in the apis directory. +func (s *apiScaffolder) scaffoldAPI( + scaffold *machinery.Scaffold, + workload kinds.WorkloadBuilder, +) error { + // scaffold the base api types + if err := scaffold.Execute( + &api.Types{Builder: workload}, + &api.Group{}, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldAPITypes.Error()) + } + + // scaffold the specific kind files + if err := scaffold.Execute( + &api.Kind{}, + &api.KindLatest{PackageName: workload.GetPackageName()}, + &api.KindUpdater{}, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldAPIKindInfo.Error()) + } + + // scaffold the resources + if err := scaffold.Execute(&resources.Resources{Builder: workload}); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldAPIResources.Error()) + } + + // scaffolds the child resource definition files + // these are the resources defined in the static yaml manifests + for _, manifest := range *workload.GetManifests() { + if err := scaffold.Execute( + &resources.Definition{Builder: workload, Manifest: manifest}, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldAPIChildResources.Error()) + } + + // update the child resource mutation for each child resource + for i := range manifest.ChildResources { + if err := scaffold.Execute( + &resources.Mutate{Builder: workload, ChildResource: manifest.ChildResources[i]}, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldAPIChildResources.Error()) + } + } + } + + // scaffold the child resource naming helpers + if err := scaffold.Execute(&resources.Constants{Builder: workload}); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldAPIResources.Error()) + } + + return nil +} + +// scaffoldCLI runs the specific logic to scaffold the companion CLI for an +// individual workload. +func (s *apiScaffolder) scaffoldCLI( + scaffold *machinery.Scaffold, + workload kinds.WorkloadBuilder, +) error { + // scaffold init subcommand + if err := scaffold.Execute( + &cli.CmdInitSub{Builder: workload}, + &cli.CmdInitSubUpdater{Builder: workload}, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldCompanionCLIInit.Error()) + } + + // scaffold the generate command unless we have a collection without resources + if (workload.HasChildResources() && workload.IsCollection()) || !workload.IsCollection() { + if err := scaffold.Execute( + &cli.CmdGenerateSub{Builder: workload}, + &cli.CmdGenerateSubUpdater{Builder: workload}, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldCompanionCLIGenerate.Error()) + } + } + + // scaffold version subcommand + if err := scaffold.Execute( + &cli.CmdVersionSub{Builder: workload}, + &cli.CmdVersionSubUpdater{Builder: workload}, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldCompanionCLIVersion.Error()) + } + + // scaffold the root command + if err := scaffold.Execute( + &cli.CmdRootUpdater{ + InitCommand: true, + GenerateCommand: true, + VersionCommand: true, + Builder: workload, + }, + ); err != nil { + return fmt.Errorf("%w; %s", err, ErrScaffoldCompanionCLIRoot.Error()) + } + + return nil +} diff --git a/internal/plugins/workload/v2/scaffolds/init.go b/internal/plugins/workload/v2/scaffolds/init.go new file mode 100644 index 0000000..56f1c3d --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/init.go @@ -0,0 +1,166 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package scaffolds + +import ( + "fmt" + + log "github.com/sirupsen/logrus" + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v4/pkg/config" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins" + + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/cli" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/config/manifests" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/config/scorecard" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/hack" + "github.com/nukleros/operator-builder/internal/plugins/workload/v2/scaffolds/templates/test/e2e" + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var _ plugins.Scaffolder = &initScaffolder{} + +type initScaffolder struct { + config config.Config + boilerplatePath string + workload kinds.WorkloadBuilder + cliRootCommandName string + controllerImg string + enableOlm bool + license string + owner string + + fs machinery.Filesystem +} + +// NewInitScaffolder returns a new Scaffolder for project initialization operations. +func NewInitScaffolder( + cfg config.Config, + workload kinds.WorkloadBuilder, + cliRootCommandName string, + controllerImg string, + enableOlm bool, + license string, + owner string, +) plugins.Scaffolder { + return &initScaffolder{ + config: cfg, + boilerplatePath: hack.DefaultBoilerplatePath, + workload: workload, + cliRootCommandName: cliRootCommandName, + controllerImg: controllerImg, + enableOlm: enableOlm, + license: license, + owner: owner, + } +} + +// InjectFS implements cmdutil.Scaffolder. +func (s *initScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// scaffold implements cmdutil.Scaffolder. +func (s *initScaffolder) Scaffold() error { + log.Println("Adding workload scaffolding...") + + // Initialize the machinery.Scaffold that will write the boilerplate file to disk + // The boilerplate file needs to be scaffolded as a separate step as it is going to + // be used by the rest of the files, even those scaffolded in this command call. + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + ) + + if s.license != "none" { + bpFile := &hack.Boilerplate{ + License: s.license, + Owner: s.owner, + } + + bpFile.Path = s.boilerplatePath + if err := scaffold.Execute(bpFile); err != nil { + return err + } + + boilerplate, err := afero.ReadFile(s.fs.FS, s.boilerplatePath) + if err != nil { + return err + } + // Initialize the machinery.Scaffold that will write the files to disk + scaffold = machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + ) + } else { + s.boilerplatePath = "" + // Initialize the machinery.Scaffold without boilerplate + scaffold = machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + ) + } + + if s.workload.HasRootCmdName() { + if err := scaffold.Execute( + &cli.Main{RootCmd: *s.workload.GetRootCommand()}, + &cli.CmdRoot{Initializer: s.workload}, + &cli.CmdInit{Initializer: s.workload}, + &cli.CmdGenerate{Initializer: s.workload}, + &cli.CmdVersion{Initializer: s.workload}, + ); err != nil { + return fmt.Errorf("unable to scaffold initial configuration for companionCli, %w", err) + } + } + + if err := scaffold.Execute( + &templates.Main{}, + &templates.GoMod{}, + &templates.Dockerfile{}, + &templates.Readme{ + RootCmdName: s.cliRootCommandName, + EnableOLM: s.enableOlm, + ControllerImg: s.controllerImg, + }, + &templates.Makefile{ + RootCmdName: s.cliRootCommandName, + ControllerImg: s.controllerImg, + EnableOLM: s.enableOlm, + KustomizeVersion: utils.KustomizeVersion, + ControllerToolsVersion: utils.ControllerToolsVersion, + OperatorSDKVersion: utils.OperatorSDKVersion, + ControllerRuntimeVersion: utils.ControllerRuntimeVersion, + EnvtestVersion: utils.EnvtestVersion, + EnvtestK8SVersion: utils.EnvtestK8SVersion, + GolangCILintVersion: utils.GolangCILintVersion, + }, + &e2e.Test{}, + ); err != nil { + return fmt.Errorf("unable to scaffold initial configuration, %w", err) + } + + if s.enableOlm { + if err := scaffold.Execute( + &scorecard.Scorecard{ScorecardType: scorecard.ScorecardTypeBase}, + &scorecard.Scorecard{ScorecardType: scorecard.ScorecardTypeKustomize}, + &scorecard.Scorecard{ScorecardType: scorecard.ScorecardTypePatchesBasic}, + &scorecard.Scorecard{ScorecardType: scorecard.ScorecardTypePatchesOLM}, + ); err != nil { + return fmt.Errorf("unable to scaffold OLM scorecard configuration, %w", err) + } + + if err := scaffold.Execute( + &manifests.Kustomization{ + SupportsKustomizeV4: false, + SupportsWebhooks: false, + }, + ); err != nil { + return fmt.Errorf("unable to scaffold manifests, %w", err) + } + } + + return nil +} diff --git a/internal/plugins/workload/v2/scaffolds/templates/api/group.go b/internal/plugins/workload/v2/scaffolds/templates/api/group.go new file mode 100644 index 0000000..6ac0f8a --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/api/group.go @@ -0,0 +1,58 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package api + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &Group{} + +// Group scaffolds the file that defines the registration methods for a certain group and version. +type Group struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin +} + +func (f *Group) SetTemplateDefaults() error { + f.Path = filepath.Join( + "apis", + f.Resource.Group, + f.Resource.Version, + "groupversion_info.go", + ) + + f.TemplateBody = groupTemplate + + return nil +} + +const groupTemplate = `{{ .Boilerplate }} + +// Package {{ .Resource.Version }} contains API Schema definitions for the {{ .Resource.Group }} {{ .Resource.Version }} API group. +//+kubebuilder:object:generate=true +//+groupName={{ .Resource.QualifiedGroup }} +package {{ .Resource.Version }} + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "{{ .Resource.QualifiedGroup }}", Version: "{{ .Resource.Version }}"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/api/kind.go b/internal/plugins/workload/v2/scaffolds/templates/api/kind.go new file mode 100644 index 0000000..2d09d96 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/api/kind.go @@ -0,0 +1,189 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package api + +import ( + "fmt" + "path/filepath" + "strings" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var ( + _ machinery.Template = &Kind{} + _ machinery.Template = &KindLatest{} + _ machinery.Inserter = &KindUpdater{} +) + +// Code Markers (associated with below fragments). +const ( + kindImportsMarker = "operator-builder:imports" + kindGroupVersionsMarker = "operator-builder:groupversions" +) + +// Code Fragments (associated with above markers). +const ( + kindImportsFragment = `%s "%s/apis/%s/%s" +` + kindGroupVersionsFragment = `%s.GroupVersion, +` +) + +// Kind scaffolds the file that defines specific information related to kind regardless of +// the API version. +type Kind struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin +} + +func (f *Kind) SetTemplateDefaults() error { + f.Path = filepath.Join( + "apis", + f.Resource.Group, + fmt.Sprintf("%s.go", strings.ToLower(f.Resource.Kind)), + ) + + f.TemplateBody = fmt.Sprintf( + kindTemplate, + machinery.NewMarkerFor(f.Path, kindImportsMarker), + machinery.NewMarkerFor(f.Path, kindGroupVersionsMarker), + ) + + return nil +} + +// KindLatest scaffolds the file that defines specific information related to the latest API version +// of a specific kind. +type KindLatest struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + machinery.RepositoryMixin + + PackageName string +} + +func (f *KindLatest) SetTemplateDefaults() error { + f.Path = filepath.Join( + "apis", + f.Resource.Group, + fmt.Sprintf("%s_latest.go", strings.ToLower(f.Resource.Kind)), + ) + + f.TemplateBody = kindLatestTemplate + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +// KindUpdater updates the file with any new version information. +type KindUpdater struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + machinery.RepositoryMixin +} + +// GetPath implements file.Builder interface. +func (f *KindUpdater) GetPath() string { + return filepath.Join( + "apis", + f.Resource.Group, + fmt.Sprintf("%s.go", strings.ToLower(f.Resource.Kind)), + ) +} + +// GetIfExistsAction implements file.Builder interface. +func (*KindUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile +} + +// GetMarkers implements file.Inserter interface. +func (f *KindUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.GetPath(), kindImportsMarker), + machinery.NewMarkerFor(f.GetPath(), kindGroupVersionsMarker), + } +} + +// GetCodeFragments implements file.Inserter interface. +func (f *KindUpdater) 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 { + return fragments + } + + versionGroup := fmt.Sprintf("%s%s", f.Resource.Version, f.Resource.Group) + + // Generate imports code fragments + imports := make([]string, 0) + imports = append(imports, fmt.Sprintf(kindImportsFragment, + versionGroup, + f.Repo, + f.Resource.Group, + f.Resource.Version, + )) + + // Generate groupVersions code fragments + groupVersions := make([]string, 0) + groupVersions = append(groupVersions, fmt.Sprintf(kindGroupVersionsFragment, versionGroup)) + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), kindImportsMarker)] = imports + } + + if len(groupVersions) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), kindGroupVersionsMarker)] = groupVersions + } + + return fragments +} + +const ( + kindTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Group }} + +import ( + %s + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// {{ .Resource.Kind }}GroupVersions returns all group version objects associated with this kind. +func {{ .Resource.Kind }}GroupVersions() []schema.GroupVersion { + return []schema.GroupVersion{ + %s + } +} +` + kindLatestTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Group }} + +import ( + {{ .Resource.Version }}{{ .Resource.Group }} "{{ .Repo }}/apis/{{ .Resource.Group }}/{{ .Resource.Version }}" + {{ .Resource.Version }}{{ lower .Resource.Kind }} "{{ .Resource.Path }}/{{ .PackageName }}" +) + +// Code generated by operator-builder. DO NOT EDIT. + +// {{ .Resource.Kind }}LatestGroupVersion returns the latest group version object associated with this +// particular kind. +var {{ .Resource.Kind }}LatestGroupVersion = {{ .Resource.Version }}{{ .Resource.Group }}.GroupVersion + +// {{ .Resource.Kind }}LatestSample returns the latest sample manifest associated with this +// particular kind. +var {{ .Resource.Kind }}LatestSample = {{ .Resource.Version }}{{ lower .Resource.Kind }}.Sample(false) +` +) diff --git a/internal/plugins/workload/v2/scaffolds/templates/api/resources/constants.go b/internal/plugins/workload/v2/scaffolds/templates/api/resources/constants.go new file mode 100644 index 0000000..3c06622 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/api/resources/constants.go @@ -0,0 +1,74 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +package resources + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var _ machinery.Template = &Constants{} + +// Types scaffolds the child resource Constants files. +type Constants struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + ConstantStrings []string +} + +func (f *Constants) SetTemplateDefaults() error { + f.Path = filepath.Join( + "apis", + f.Resource.Group, + f.Resource.Version, + f.Builder.GetPackageName(), + "constants", + "names.go", + ) + + children := kinds.GetWorkloadChildren(f.Builder) + + for i := range children { + child := children[i] + + if child.NameConstant() != "" { + f.ConstantStrings = append( + f.ConstantStrings, + fmt.Sprintf("%s = %q", child.UniqueName, child.NameConstant()), + ) + } + } + + f.TemplateBody = NamesTemplate + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +const NamesTemplate = `{{ .Boilerplate }} + +package constants + +{{ if .Builder.HasChildResources }} +// this package includes the constants which include the resource names. it is a standalone +// package to prevent import cycle errors when attempting to reference the names from other +// packages (e.g. mutate). +const ( + {{ range .ConstantStrings }} + {{- . }} + {{ end }} +) +{{ end }} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/api/resources/definition.go b/internal/plugins/workload/v2/scaffolds/templates/api/resources/definition.go new file mode 100644 index 0000000..bca8ab6 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/api/resources/definition.go @@ -0,0 +1,108 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package resources + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" + "github.com/nukleros/operator-builder/internal/workload/v1/manifests" +) + +var _ machinery.Template = &Definition{} + +// Types scaffolds the child resource definition files. +type Definition struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder + Manifest *manifests.Manifest + + // template fields + UseStrConv bool +} + +func (f *Definition) SetTemplateDefaults() error { + f.Path = filepath.Join( + "apis", + f.Resource.Group, + f.Resource.Version, + f.Builder.GetPackageName(), + f.Manifest.SourceFilename, + ) + + // determine if we need to import the strconv package + for i := range f.Manifest.ChildResources { + if f.Manifest.ChildResources[i].UseStrConv { + f.UseStrConv = true + + break + } + } + + f.TemplateBody = definitionTemplate + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +//nolint:lll +const definitionTemplate = `{{ .Boilerplate }} + +package {{ .Builder.GetPackageName }} + +import ( + {{ if .UseStrConv }}"strconv"{{ end }} + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/nukleros/operator-builder-tools/pkg/controller/workload" + + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" + "{{ .Resource.Path }}/{{ .Builder.GetPackageName }}/mutate" + {{- if .Builder.IsComponent }} + {{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }} "{{ .Repo }}/apis/{{ .Builder.GetCollection.Spec.API.Group }}/{{ .Builder.GetCollection.Spec.API.Version }}" + {{ end -}} +) + +{{ range .Manifest.ChildResources }} +{{ range .RBAC }} +{{- .ToMarker }} +{{ end }} +// {{ .CreateFuncName }} creates the {{ .Kind }} resource with name {{ .NameComment }}. +func {{ .CreateFuncName }} ( + parent *{{ $.Resource.ImportAlias }}.{{ $.Resource.Kind }}, + {{ if $.Builder.IsComponent -}} + collection *{{ $.Builder.GetCollection.Spec.API.Group }}{{ $.Builder.GetCollection.Spec.API.Version }}.{{ $.Builder.GetCollection.Spec.API.Kind }}, + {{ end -}} + reconciler workload.Reconciler, + req *workload.Request, +) ([]client.Object, error) { + + {{ range .IncludeCode }} + {{ . }} + {{ end }} + + {{- .SourceCode }} + + {{ if not $.Builder.IsClusterScoped }} + resourceObj.SetNamespace(parent.Namespace) + {{ end }} + + {{ if $.Builder.IsComponent -}} + return mutate.{{ .MutateFuncName }}(resourceObj, parent, collection, reconciler, req) + {{ else -}} + return mutate.{{ .MutateFuncName }}(resourceObj, parent, reconciler, req) + {{ end -}} +} +{{ end }} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/api/resources/mutate.go b/internal/plugins/workload/v2/scaffolds/templates/api/resources/mutate.go new file mode 100644 index 0000000..57383b0 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/api/resources/mutate.go @@ -0,0 +1,83 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +package resources + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" + "github.com/nukleros/operator-builder/internal/workload/v1/manifests" +) + +var ( + _ machinery.Template = &Mutate{} +) + +// Mutate scaffolds the root command file for the companion CLI. +type Mutate struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin + + // input variables + Builder kinds.WorkloadBuilder + ChildResource manifests.ChildResource +} + +func (f *Mutate) SetTemplateDefaults() error { + // set interface variables + f.Path = filepath.Join( + "apis", + f.Resource.Group, + f.Resource.Version, + f.Builder.GetPackageName(), + "mutate", + f.ChildResource.MutateFileName(), + ) + + f.TemplateBody = MutateTemplate + + return nil +} + +// GetIfExistsAction implements file.Builder interface. +func (*Mutate) GetIfExistsAction() machinery.IfExistsAction { + return machinery.SkipFile +} + +//nolint:lll +const MutateTemplate = `{{ .Boilerplate }} + +package mutate + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/nukleros/operator-builder-tools/pkg/controller/workload" + + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" + {{- if .Builder.IsComponent }} + {{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }} "{{ .Repo }}/apis/{{ .Builder.GetCollection.Spec.API.Group }}/{{ .Builder.GetCollection.Spec.API.Version }}" + {{ end -}} +) + +// {{ .ChildResource.MutateFuncName }} mutates the {{ .ChildResource.Kind }} resource with name {{ .ChildResource.NameComment }}. +func {{ .ChildResource.MutateFuncName }} ( + original client.Object, + parent *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}, {{ if .Builder.IsComponent }}collection *{{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }},{{ end }} + reconciler workload.Reconciler, req *workload.Request, +) ([]client.Object, error) { + // if either the reconciler or request are found to be nil, return the base object. + if reconciler == nil || req == nil { + return []client.Object{original}, nil + } + + // mutation logic goes here + + return []client.Object{original}, nil +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/api/resources/resources.go b/internal/plugins/workload/v2/scaffolds/templates/api/resources/resources.go new file mode 100644 index 0000000..fca2a28 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/api/resources/resources.go @@ -0,0 +1,234 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package resources + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/plugins/workload/v1/scaffolds/templates/config/samples" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var _ machinery.Template = &Resources{} + +// Types scaffolds child resource creation functions. +type Resources struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + SpecFields *kinds.APIFields + IsClusterScoped bool + CreateFuncNames []string + InitFuncNames []string +} + +func (f *Resources) SetTemplateDefaults() error { + // set template fields + f.CreateFuncNames, f.InitFuncNames = f.Builder.GetManifests().FuncNames() + f.SpecFields = f.Builder.GetAPISpecFields() + f.IsClusterScoped = f.Builder.IsClusterScoped() + + // set interface fields + f.Path = filepath.Join( + "apis", + f.Resource.Group, + f.Resource.Version, + f.Builder.GetPackageName(), + "resources.go", + ) + + f.TemplateBody = fmt.Sprintf(resourcesTemplate, + samples.SampleTemplate, + samples.SampleTemplateRequiredOnly, + ) + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +//nolint:lll +const resourcesTemplate = `{{ .Boilerplate }} + +package {{ .Builder.GetPackageName }} + +import ( + {{ if ne .Builder.GetRootCommand.Name "" }}"fmt"{{ end }} + + {{ if ne .Builder.GetRootCommand.Name "" }}"sigs.k8s.io/yaml"{{ end }} + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/nukleros/operator-builder-tools/pkg/controller/workload" + + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" + {{- if .Builder.IsComponent }} + {{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }} "{{ .Repo }}/apis/{{ .Builder.GetCollection.Spec.API.Group }}/{{ .Builder.GetCollection.Spec.API.Version }}" + {{ end -}} +) + +// sample{{ .Resource.Kind }} is a sample containing all fields +const sample{{ .Resource.Kind }} = ` + "`" + `%s` + "`" + ` + +// sample{{ .Resource.Kind }}Required is a sample containing only required fields +const sample{{ .Resource.Kind }}Required = ` + "`" + `%s` + "`" + ` + +// Sample returns the sample manifest for this custom resource. +func Sample(requiredOnly bool) string { + if requiredOnly { + return sample{{ .Resource.Kind }}Required + } + + return sample{{ .Resource.Kind }} +} + +// Generate returns the child resources that are associated with this workload given +// appropriate structured inputs. +{{ if .Builder.IsComponent -}} +func Generate( + workloadObj {{ .Resource.ImportAlias }}.{{ .Resource.Kind }}, + collectionObj {{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }}, +{{ else if .Builder.IsCollection -}} +func Generate( + collectionObj {{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }}, +{{ else -}} +func Generate( + workloadObj {{ .Resource.ImportAlias }}.{{ .Resource.Kind }}, +{{ end -}} + reconciler workload.Reconciler, + req *workload.Request, +) ([]client.Object, error) { + resourceObjects := []client.Object{} + + for _, f := range CreateFuncs { + {{ if .Builder.IsComponent -}} + resources, err := f(&workloadObj, &collectionObj, reconciler, req) + {{ else if .Builder.IsCollection -}} + resources, err := f(&collectionObj, reconciler, req) + {{ else -}} + resources, err := f(&workloadObj, reconciler, req) + {{ end }} + if err != nil { + return nil, err + } + + resourceObjects = append(resourceObjects, resources...) + } + + return resourceObjects, nil +} + +{{ if ne .Builder.GetRootCommand.Name "" }} +// GenerateForCLI returns the child resources that are associated with this workload given +// appropriate YAML manifest files. +func GenerateForCLI( + {{- if or (.Builder.IsStandalone) (.Builder.IsComponent) }}workloadFile []byte,{{ end -}} + {{- if or (.Builder.IsComponent) (.Builder.IsCollection) }}collectionFile []byte,{{ end -}} +) ([]client.Object, error) { + {{- if or (.Builder.IsStandalone) (.Builder.IsComponent) }} + var workloadObj {{ .Resource.ImportAlias }}.{{ .Resource.Kind }} + if err := yaml.Unmarshal(workloadFile, &workloadObj); err != nil { + return nil, fmt.Errorf("failed to unmarshal yaml into workload, %%w", err) + } + + if err := workload.Validate(&workloadObj); err != nil { + return nil, fmt.Errorf("error validating workload yaml, %%w", err) + } + {{ end }} + + {{- if or (.Builder.IsComponent) (.Builder.IsCollection) }} + var collectionObj {{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }} + if err := yaml.Unmarshal(collectionFile, &collectionObj); err != nil { + return nil, fmt.Errorf("failed to unmarshal yaml into collection, %%w", err) + } + + if err := workload.Validate(&collectionObj); err != nil { + return nil, fmt.Errorf("error validating collection yaml, %%w", err) + } + {{ end }} + + {{ if .Builder.IsComponent }} + return Generate(workloadObj, collectionObj, nil, nil) + {{ else if .Builder.IsCollection }} + return Generate(collectionObj, nil, nil) + {{ else }} + return Generate(workloadObj, nil, nil) + {{ end -}} +} +{{ end }} + +// CreateFuncs is an array of functions that are called to create the child resources for the controller +// in memory during the reconciliation loop prior to persisting the changes or updates to the Kubernetes +// database. +var CreateFuncs = []func( + *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}, + {{ if $.Builder.IsComponent -}} + *{{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }}, + {{ end -}} + workload.Reconciler, + *workload.Request, +) ([]client.Object, error) { + {{ range .CreateFuncNames }} + {{- . -}}, + {{ end }} +} + +// InitFuncs is an array of functions that are called prior to starting the controller manager. This is +// necessary in instances which the controller needs to "own" objects which depend on resources to +// pre-exist in the cluster. A common use case for this is the need to own a custom resource. +// If the controller needs to own a custom resource type, the CRD that defines it must +// first exist. In this case, the InitFunc will create the CRD so that the controller +// can own custom resources of that type. Without the InitFunc the controller will +// crash loop because when it tries to own a non-existent resource type during manager +// setup, it will fail. +var InitFuncs = []func( + *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}, + {{ if $.Builder.IsComponent -}} + *{{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }}, + {{ end -}} + workload.Reconciler, + *workload.Request, +) ([]client.Object, error) { + {{ range .InitFuncNames }} + {{- . -}}, + {{ end }} +} + +{{ if $.Builder.IsComponent -}} +func ConvertWorkload(component, collection workload.Workload) ( + *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}, + *{{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }}, + error, +) { +{{- else }} +func ConvertWorkload(component workload.Workload) (*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}, error) { +{{- end }} + p, ok := component.(*{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}) + if !ok { + {{- if $.Builder.IsComponent }} + return nil, nil, {{ .Resource.ImportAlias }}.ErrUnableToConvert{{ .Resource.Kind }} + } + + c, ok := collection.(*{{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }}) + if !ok { + return nil, nil, {{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.ErrUnableToConvert{{ .Builder.GetCollection.Spec.API.Kind }} + } + + return p, c, nil +{{- else }} + return nil, {{ .Resource.ImportAlias }}.ErrUnableToConvert{{ .Resource.Kind }} + } + + return p, nil +{{- end }} +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/api/types.go b/internal/plugins/workload/v2/scaffolds/templates/api/types.go new file mode 100644 index 0000000..88b1293 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/api/types.go @@ -0,0 +1,197 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package api + +import ( + "fmt" + "path/filepath" + "strings" + "text/template" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var _ machinery.Template = &Types{} + +// Types scaffolds a workload's API type. +type Types struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder +} + +// SetTemplateDefaults implements file.Template. +func (f *Types) SetTemplateDefaults() error { + f.Path = filepath.Join( + "apis", + f.Resource.Group, + f.Resource.Version, + fmt.Sprintf("%s_types.go", strings.ToLower(f.Resource.Kind)), + ) + + f.TemplateBody = typesTemplate + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +func (*Types) GetFuncMap() template.FuncMap { + return utils.ContainsStringHelper() +} + +const typesTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Version }} + +import ( + "errors" + + "github.com/nukleros/operator-builder-tools/pkg/status" + "github.com/nukleros/operator-builder-tools/pkg/controller/workload" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + {{- $Repo := .Repo }}{{- $Added := "" }}{{- range .Builder.GetDependencies }} + {{- if ne .Spec.API.Group $.Resource.Group }} + {{- if not (containsString (printf "%s%s" .Spec.API.Group .Spec.API.Version) $Added) }} + {{- $Added = (printf "%s%s" $Added (printf "%s%s" .Spec.API.Group .Spec.API.Version)) }} + {{ .Spec.API.Group }}{{ .Spec.API.Version }} "{{ $Repo }}/apis/{{ .Spec.API.Group }}/{{ .Spec.API.Version }}" + {{ end }} + {{ end }} + {{ end }} +) + +var ErrUnableToConvert{{ .Resource.Kind }} = errors.New("unable to convert to {{ .Resource.Kind }}") + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +{{ .Builder.GetAPISpecFields.GenerateAPISpec .Resource.Kind }} + +// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }}. +type {{ .Resource.Kind }}Status struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + Created bool ` + "`" + `json:"created,omitempty"` + "`" + ` + DependenciesSatisfied bool ` + "`" + `json:"dependenciesSatisfied,omitempty"` + "`" + ` + Conditions []*status.PhaseCondition ` + "`" + `json:"conditions,omitempty"` + "`" + ` + Resources []*status.ChildResource ` + "`" + `json:"resources,omitempty"` + "`" + ` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +{{- if .Builder.IsClusterScoped }} +// +kubebuilder:resource:scope=Cluster +{{ end }} + +// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API. +type {{ .Resource.Kind }} struct { + metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` + metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` + Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec,omitempty"` + "`" + ` + Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty"` + "`" + ` +} + +// +kubebuilder:object:root=true + +// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }}. +type {{ .Resource.Kind }}List struct { + metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` + metav1.ListMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` + Items []{{ .Resource.Kind }} ` + "`" + `json:"items"` + "`" + ` +} + +// interface methods + +// GetReadyStatus returns the ready status for a component. +func (component *{{ .Resource.Kind }}) GetReadyStatus() bool { + return component.Status.Created +} + +// SetReadyStatus sets the ready status for a component. +func (component *{{ .Resource.Kind }}) SetReadyStatus(ready bool) { + component.Status.Created = ready +} + +// GetDependencyStatus returns the dependency status for a component. +func (component *{{ .Resource.Kind }}) GetDependencyStatus() bool { + return component.Status.DependenciesSatisfied +} + +// SetDependencyStatus sets the dependency status for a component. +func (component *{{ .Resource.Kind }}) SetDependencyStatus(dependencyStatus bool) { + component.Status.DependenciesSatisfied = dependencyStatus +} + +// GetPhaseConditions returns the phase conditions for a component. +func (component *{{ .Resource.Kind }}) GetPhaseConditions() []*status.PhaseCondition { + return component.Status.Conditions +} + +// SetPhaseCondition sets the phase conditions for a component. +func (component *{{ .Resource.Kind }}) SetPhaseCondition(condition *status.PhaseCondition) { + for i, currentCondition := range component.GetPhaseConditions() { + if currentCondition.Phase == condition.Phase { + component.Status.Conditions[i] = condition + + return + } + } + + // phase not found, lets add it to the list. + component.Status.Conditions = append(component.Status.Conditions, condition) +} + +// GetResources returns the child resource status for a component. +func (component *{{ .Resource.Kind }}) GetChildResourceConditions() []*status.ChildResource { + return component.Status.Resources +} + +// SetResources sets the phase conditions for a component. +func (component *{{ .Resource.Kind }}) SetChildResourceCondition(resource *status.ChildResource) { + for i, currentResource := range component.GetChildResourceConditions() { + if currentResource.Group == resource.Group && currentResource.Version == resource.Version && currentResource.Kind == resource.Kind { + if currentResource.Name == resource.Name && currentResource.Namespace == resource.Namespace { + component.Status.Resources[i] = resource + + return + } + } + } + + // phase not found, lets add it to the collection + component.Status.Resources = append(component.Status.Resources, resource) +} + +// GetDependencies returns the dependencies for a component. +func (*{{ .Resource.Kind }}) GetDependencies() []workload.Workload { + return []workload.Workload{ + {{- range .Builder.GetDependencies }} + {{- if eq .Spec.API.Group $.Resource.Group }} + &{{ .Spec.API.Kind }}{}, + {{- else }} + &{{ .Spec.API.Group }}{{ .Spec.API.Version }}.{{ .Spec.API.Kind }}{}, + {{- end }} + {{- end }} + } +} + +// GetComponentGVK returns a GVK object for the component. +func (*{{ .Resource.Kind }}) GetWorkloadGVK() schema.GroupVersionKind { + return GroupVersion.WithKind("{{ .Resource.Kind }}") +} + +func init() { + SchemeBuilder.Register(&{{ .Resource.Kind }}{}, &{{ .Resource.Kind }}List{}) +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_generate.go b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_generate.go new file mode 100644 index 0000000..07acf0f --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_generate.go @@ -0,0 +1,167 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package cli + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +const ( + generateCommandName = "generate" + generateCommandDescr = "generate child resource manifests from a workload's custom resource" +) + +var _ machinery.Template = &CmdGenerate{} + +// CmdGenerate scaffolds the companion CLI's generate subcommand for +// component workloads. The generate logic will live in the workload's +// subcommand to this command; see cmd_generate_sub.go. +type CmdGenerate struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + + // input variables + Initializer kinds.WorkloadBuilder + + // template variables + GenerateCommandName string + GenerateCommandDescr string +} + +func (f *CmdGenerate) SetTemplateDefaults() error { + // set template variables + f.GenerateCommandName = generateCommandName + f.GenerateCommandDescr = generateCommandDescr + + // set interface variables + f.Path = filepath.Join("cmd", f.Initializer.GetRootCommand().Name, "commands", "generate", "generate.go") + f.TemplateBody = cliCmdGenerateTemplate + + return nil +} + +const cliCmdGenerateTemplate = `{{ .Boilerplate }} + +package generate + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +type GenerateFunc func(*GenerateSubCommand) error + +type GenerateSubCommand struct { + *cobra.Command + + // flags + WorkloadManifest string + CollectionManifest string + APIVersion string + + // options + Name string + Description string + CollectionKind string + UseCollectionManifest bool + WorkloadKind string + UseWorkloadManifest bool + SubCommandOf *cobra.Command + + // execution + GenerateFunc GenerateFunc +} + +{{ if .Initializer.IsCollection }} +// NewBaseGenerateSubCommand returns a subcommand that is meant to belong to a parent +// subcommand but have subcommands itself. +func NewBaseGenerateSubCommand(parentCommand *cobra.Command) *GenerateSubCommand { + generateCmd := &GenerateSubCommand{ + Name: "{{ .GenerateCommandName }}", + Description: "{{ .GenerateCommandDescr }}", + UseCollectionManifest: false, + UseWorkloadManifest: false, + SubCommandOf: parentCommand, + } + + generateCmd.Setup() + + return generateCmd +} +{{ end }} + +// Setup sets up this command to be used as a command. +func (g *GenerateSubCommand) Setup() { + g.Command = &cobra.Command{ + Use: g.Name, + Short: g.Description, + Long: g.Description, + } + + // run the generate function if the function signature is set + if g.GenerateFunc != nil { + g.RunE = g.generate + } + + // add workload-manifest flag if this subcommand requests it + if g.UseWorkloadManifest { + g.Flags().StringVarP( + &g.WorkloadManifest, + "workload-manifest", + "w", + "", + fmt.Sprintf("filepath to the %s workload manifest used to generate child resources", g.WorkloadKind), + ) + + if err := g.MarkFlagRequired("workload-manifest"); err != nil { + panic(err) + } + } + + // add collection-manifest flag if this subcommand requests it + if g.UseCollectionManifest { + g.Command.Flags().StringVarP( + &g.CollectionManifest, + "collection-manifest", + "c", + "", + fmt.Sprintf("filepath to the %s collection manifest used to generate child resources", g.CollectionKind), + ) + + if err := g.MarkFlagRequired("collection-manifest"); err != nil { + panic(err) + } + } + + // add this as a subcommand of another command if set + if g.SubCommandOf != nil { + g.SubCommandOf.AddCommand(g.Command) + } +} + +// GetParent is a convenience function written when the CLI code is scaffolded +// to return the parent command and avoid scaffolding code with bad imports. +func GetParent(c interface{}) *cobra.Command { + switch subcommand := c.(type) { + case *GenerateSubCommand: + return subcommand.Command + case *cobra.Command: + return subcommand + } + + panic(fmt.Sprintf("subcommand is not proper type: %T", c)) +} + +// generate creates child resource manifests from a workload's custom resource. +func (g *GenerateSubCommand) generate(cmd *cobra.Command, args []string) error { + return g.GenerateFunc(g) +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_generate_sub.go b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_generate_sub.go new file mode 100644 index 0000000..dadd832 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_generate_sub.go @@ -0,0 +1,332 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package cli + +import ( + "fmt" + "strings" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/commands/companion" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var _ machinery.Template = &CmdGenerateSub{} +var _ machinery.Inserter = &CmdGenerateSubUpdater{} + +// cmdGenerateSubCommon include the common fields that are shared by all generate +// subcommand structs for templating purposes. +type cmdGenerateSubCommon struct { + RootCmd companion.CLI + SubCmd companion.CLI + Collection *kinds.WorkloadCollection + + UseCollectionManifestFlag bool + UseWorkloadManifestFlag bool +} + +// CmdGenerateSub scaffolds the companion CLI's generate subcommand for the +// workload. This where the actual generate logic lives. +type CmdGenerateSub struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + cmdGenerateSubCommon + GenerateCommandName string + GenerateCommandDescr string + GenerateFuncInputs string +} + +func (f *CmdGenerateSub) SetTemplateDefaults() error { + // set template fields + f.RootCmd = *f.Builder.GetRootCommand() + f.SubCmd = *f.Builder.GetSubCommand() + f.Collection = f.Builder.GetCollection() + + // if we have a standalone simply use the default command name and description + // for generate since the 'generate' command will be the last in the chain, + // otherwise we will use the requested subcommand name. + if f.Builder.IsStandalone() { + f.GenerateCommandName = generateCommandName + f.GenerateCommandDescr = generateCommandDescr + } else { + f.GenerateCommandName = f.SubCmd.Name + f.GenerateCommandDescr = f.SubCmd.Description + f.UseCollectionManifestFlag = true + } + + // use the workload manifest flag for non-collection use cases + if !f.Builder.IsCollection() { + f.UseWorkloadManifestFlag = true + } + + // determine the input string to the generated function + switch { + case f.UseCollectionManifestFlag && f.UseWorkloadManifestFlag: + f.GenerateFuncInputs = "workloadFile, collectionFile" + case f.UseCollectionManifestFlag && !f.UseWorkloadManifestFlag: + f.GenerateFuncInputs = "collectionFile" + default: + f.GenerateFuncInputs = "workloadFile" + } + + // set interface fields + f.Path = f.SubCmd.GetSubCmdRelativeFileName( + f.RootCmd.Name, + "generate", + f.Resource.Group, + utils.ToFileName(f.Resource.Kind), + ) + + f.TemplateBody = fmt.Sprintf( + cmdGenerateSub, + machinery.NewMarkerFor(f.Path, generateImportMarker), + machinery.NewMarkerFor(f.Path, generateVersionMapMarker), + ) + + return nil +} + +// CmdGenerateSubUpdater updates a specific components version subcommand with +// appropriate initialization information. +type CmdGenerateSubUpdater struct { //nolint:maligned + machinery.RepositoryMixin + machinery.MultiGroupMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + cmdGenerateSubCommon + PackageName string +} + +// GetPath implements file.Builder interface. +func (f *CmdGenerateSubUpdater) GetPath() string { + return f.SubCmd.GetSubCmdRelativeFileName( + f.Builder.GetRootCommand().Name, + "generate", + f.Resource.Group, + utils.ToFileName(f.Resource.Kind), + ) +} + +// GetIfExistsAction implements file.Builder interface. +func (*CmdGenerateSubUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile +} + +const generateImportMarker = "operator-builder:imports" +const generateVersionMapMarker = "operator-builder:versionmap" + +// GetMarkers implements file.Inserter interface. +func (f *CmdGenerateSubUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.GetPath(), generateImportMarker), + machinery.NewMarkerFor(f.GetPath(), generateVersionMapMarker), + } +} + +// Code Fragments. +const ( + // this fragment is the imports which are created and updated upon each new + // api version that is created. + generateImportFragment = `%s%s "%s" +` + // generateMapFragment is a fragment which provides a new switch for each api version + // that is created for use by the api-version flag. + generateMapFragment = `"%s": %s%s.GenerateForCLI, +` +) + +// GetCodeFragments implements file.Inserter interface. +func (f *CmdGenerateSubUpdater) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 1) + + // set template fields + f.RootCmd = *f.Builder.GetRootCommand() + f.SubCmd = *f.Builder.GetSubCommand() + f.PackageName = f.Builder.GetPackageName() + f.Collection = f.Builder.GetCollection() + + // If resource is not being provided we are creating the file, not updating it + if f.Resource == nil { + return fragments + } + + // use the collection flag for non-standalone use cases + if !f.Builder.IsStandalone() { + f.UseCollectionManifestFlag = true + } + + // use the workload manifest flag for non-collection use cases + if !f.Builder.IsCollection() { + f.UseWorkloadManifestFlag = true + } + + // Generate subCommands code fragments + imports := make([]string, 0) + switches := make([]string, 0) + + // add the imports + imports = append(imports, fmt.Sprintf(generateImportFragment, + f.Resource.Version, + strings.ToLower(f.Resource.Kind), + fmt.Sprintf("%s/%s", f.Resource.Path, f.Builder.GetPackageName()), + )) + + // add the switches fragment + switches = append(switches, fmt.Sprintf(generateMapFragment, + f.Resource.Version, f.Resource.Version, strings.ToLower(f.Resource.Kind))) + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), generateImportMarker)] = imports + } + + if len(switches) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), generateVersionMapMarker)] = switches + } + + return fragments +} + +const ( + cmdGenerateSub = `{{ .Boilerplate }} + +package {{ .Resource.Group }} + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/runtime/serializer/json" + "sigs.k8s.io/controller-runtime/pkg/client" + + // common imports for subcommands + cmdgenerate "{{ .Repo }}/cmd/{{ .RootCmd.Name }}/commands/generate" + + // specific imports for workloads + {{- if .Builder.IsComponent }} + {{ .Collection.Spec.API.Group }}{{ .Collection.Spec.API.Version }} "{{ .Repo }}/apis/{{ .Collection.Spec.API.Group }}/{{ .Collection.Spec.API.Version }}" + {{ end }} + %s +) + +// New{{ .Resource.Kind }}SubCommand creates a new command and adds it to its +// parent command. +func New{{ .Resource.Kind }}SubCommand(parentCommand *cobra.Command) { + generateCmd := &cmdgenerate.GenerateSubCommand{ + Name: "{{ .GenerateCommandName }}", + Description: "{{ .GenerateCommandDescr }}", + SubCommandOf: parentCommand, + GenerateFunc: Generate{{ .Resource.Kind }}, + {{- if .UseCollectionManifestFlag }} + UseCollectionManifest: true, + {{- if .Builder.IsCollection }} + CollectionKind: "{{ .Resource.Kind }}", + {{- else }} + CollectionKind: "{{ .Collection.Spec.API.Kind }}", + {{ end -}} + {{ end -}} + {{ if .UseWorkloadManifestFlag -}} + UseWorkloadManifest: true, + WorkloadKind: "{{ .Resource.Kind }}", + {{ end -}} + } + + generateCmd.Setup() +} + +// Generate{{ .Resource.Kind }} runs the logic to generate child resources for a +// {{ .Resource.Kind }} workload. +func Generate{{ .Resource.Kind }}(g *cmdgenerate.GenerateSubCommand) error { + var apiVersion string + + {{ if .UseWorkloadManifestFlag }} + workloadFilename, _ := filepath.Abs(g.WorkloadManifest) + workloadFile, err := os.ReadFile(workloadFilename) + if err != nil { + return fmt.Errorf("failed to open workload file %%s, %%w", workloadFile, err) + } + + var workload map[string]interface{} + + if err := yaml.Unmarshal(workloadFile, &workload); err != nil { + return fmt.Errorf("failed to unmarshal yaml into workload, %%w", err) + } + + workloadGroupVersion := strings.Split(workload["apiVersion"].(string), "/") + workloadAPIVersion := workloadGroupVersion[len(workloadGroupVersion)-1] + + apiVersion = workloadAPIVersion + {{ end }} + + {{ if .UseCollectionManifestFlag }} + collectionFilename, _ := filepath.Abs(g.CollectionManifest) + collectionFile, err := os.ReadFile(collectionFilename) + if err != nil { + return fmt.Errorf("failed to open collection file %%s, %%w", collectionFile, err) + } + + var collection map[string]interface{} + + if err := yaml.Unmarshal(collectionFile, &collection); err != nil { + return fmt.Errorf("failed to unmarshal yaml into collection, %%w", err) + } + + collectionGroupVersion := strings.Split(collection["apiVersion"].(string), "/") + collectionAPIVersion := collectionGroupVersion[len(collectionGroupVersion)-1] + + apiVersion = collectionAPIVersion + {{ end }} + + // generate a map of all versions to generate functions for each api version created + {{- if .Builder.IsComponent }} + type generateFunc func([]byte, []byte) ([]client.Object, error) + {{ else }} + type generateFunc func([]byte) ([]client.Object, error) + {{ end -}} + generateFuncMap := map[string]generateFunc{ + %s + } + + generate := generateFuncMap[apiVersion] + resourceObjects, err := generate({{ .GenerateFuncInputs }}) + if err != nil { + return fmt.Errorf("unable to retrieve resources; %%w", err) + } + + e := json.NewYAMLSerializer(json.DefaultMetaFactory, nil, nil) + + outputStream := os.Stdout + + for _, o := range resourceObjects { + if _, err := outputStream.WriteString("---\n"); err != nil { + return fmt.Errorf("failed to write output, %%w", err) + } + + if err := e.Encode(o, os.Stdout); err != nil { + return fmt.Errorf("failed to write output, %%w", err) + } + } + + return nil +} +` +) diff --git a/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_init.go b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_init.go new file mode 100644 index 0000000..654edd5 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_init.go @@ -0,0 +1,146 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package cli + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +const ( + initCommandName = "init" + initCommandDescr = "write a sample custom resource manifest for a workload to standard out" +) + +var _ machinery.Template = &CmdInit{} + +// CmdInit scaffolds the companion CLI's init subcommand for +// component workloads. The init logic will live in the workload's +// subcommand to this command; see cmd_init_sub.go. +type CmdInit struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + + // input variables + Initializer kinds.WorkloadBuilder + + // template variables + InitCommandName string + InitCommandDescr string +} + +func (f *CmdInit) SetTemplateDefaults() error { + // set the template variables + f.InitCommandName = initCommandName + f.InitCommandDescr = initCommandDescr + + // set interface variables + f.Path = filepath.Join("cmd", f.Initializer.GetRootCommand().Name, "commands", "init", "init.go") + f.TemplateBody = cliCmdInitTemplate + + return nil +} + +const cliCmdInitTemplate = `{{ .Boilerplate }} + +package init + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +type InitFunc func(*InitSubCommand) error + +type InitSubCommand struct { + *cobra.Command + + // flags + APIVersion string + RequiredOnly bool + + // options + Name string + Description string + SubCommandOf *cobra.Command + + InitFunc InitFunc +} + +{{ if .Initializer.IsCollection }} +// NewBaseInitSubCommand returns a subcommand that is meant to belong to a parent +// subcommand but have subcommands itself. +func NewBaseInitSubCommand(parentCommand *cobra.Command) *InitSubCommand { + initCmd := &InitSubCommand{ + Name: "{{ .InitCommandName }}", + Description: "{{ .InitCommandDescr }}", + SubCommandOf: parentCommand, + } + + initCmd.Setup() + + return initCmd +} +{{ end }} + +// Setup sets up this command to be used as a command. +func (i *InitSubCommand) Setup() { + i.Command = &cobra.Command{ + Use: i.Name, + Short: i.Description, + Long: i.Description, + } + + // run the initialize function if the function signature is set + if i.InitFunc != nil { + i.RunE = i.initialize + } + + // always add the api-version flag + i.Flags().StringVarP( + &i.APIVersion, + "api-version", + "", + "", + "api version of the workload to generate a workload manifest for", + ) + + // always add the required-only flag + i.Flags().BoolVarP( + &i.RequiredOnly, + "required-only", + "r", + false, + "only print required fields in the manifest output", + ) + + // add this as a subcommand of another command if set + if i.SubCommandOf != nil { + i.SubCommandOf.AddCommand(i.Command) + } +} + +// GetParent is a convenience function written when the CLI code is scaffolded +// to return the parent command and avoid scaffolding code with bad imports. +func GetParent(c interface{}) *cobra.Command { + switch subcommand := c.(type) { + case *InitSubCommand: + return subcommand.Command + case *cobra.Command: + return subcommand + } + + panic(fmt.Sprintf("subcommand is not proper type: %T", c)) +} + +// initialize creates sample workload manifests for a workload's custom resource. +func (i *InitSubCommand) initialize(cmd *cobra.Command, args []string) error { + return i.InitFunc(i) +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_init_sub.go b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_init_sub.go new file mode 100644 index 0000000..e7cde6f --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_init_sub.go @@ -0,0 +1,242 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package cli + +import ( + "fmt" + "strings" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/commands/companion" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var _ machinery.Template = &CmdInitSub{} +var _ machinery.Inserter = &CmdInitSubUpdater{} + +// cmdInitSubCommon include the common fields that are shared by all init +// subcommand structs for templating purposes. +type cmdInitSubCommon struct { + RootCmd companion.CLI + SubCmd companion.CLI +} + +// CmdInitSub scaffolds the companion CLI's init subcommand for the +// workload. This where the actual init logic lives. +type CmdInitSub struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + machinery.RepositoryMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + cmdInitSubCommon + InitCommandName string + InitCommandDescr string +} + +func (f *CmdInitSub) SetTemplateDefaults() error { + // set template fields + f.RootCmd = *f.Builder.GetRootCommand() + f.SubCmd = *f.Builder.GetSubCommand() + + if f.Builder.IsStandalone() { + f.InitCommandName = initCommandName + f.InitCommandDescr = initCommandDescr + } else { + f.InitCommandName = f.SubCmd.Name + f.InitCommandDescr = f.SubCmd.Description + } + + // set interface fields + f.Path = f.SubCmd.GetSubCmdRelativeFileName( + f.RootCmd.Name, + "init", + f.Resource.Group, + utils.ToFileName(f.Resource.Kind), + ) + + f.TemplateBody = fmt.Sprintf( + cmdInitSub, + machinery.NewMarkerFor(f.Path, initImportsMarker), + machinery.NewMarkerFor(f.Path, initVersionMapMarker), + ) + + return nil +} + +// CmdInitSubUpdater updates a specific components init subcommand with +// appropriate initialization information. +type CmdInitSubUpdater struct { //nolint:maligned + machinery.RepositoryMixin + machinery.MultiGroupMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + cmdInitSubCommon +} + +// GetPath implements file.Builder interface. +func (f *CmdInitSubUpdater) GetPath() string { + return f.SubCmd.GetSubCmdRelativeFileName( + f.Builder.GetRootCommand().Name, + "init", + f.Resource.Group, + utils.ToFileName(f.Resource.Kind), + ) +} + +// GetIfExistsAction implements file.Builder interface. +func (*CmdInitSubUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile +} + +const initImportsMarker = "operator-builder:imports" +const initVersionMapMarker = "operator-builder:versionmap" + +// GetMarkers implements file.Inserter interface. +func (f *CmdInitSubUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.GetPath(), initImportsMarker), + machinery.NewMarkerFor(f.GetPath(), initVersionMapMarker), + } +} + +// Code Fragments. +const ( + // initImportsFragment is a fragment which provides the package to import + // for the workload. + initImportsFragment = `%s%s "%s" +` + + // initSwitchFragment is a fragment which provides a new switch for each api version + // that is created for use by the api-version flag. + initVersionMapFragment = `"%s": %s, +` +) + +// GetCodeFragments implements file.Inserter interface. +func (f *CmdInitSubUpdater) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 1) + + // set template fields + f.RootCmd = *f.Builder.GetRootCommand() + f.SubCmd = *f.Builder.GetSubCommand() + + // If resource is not being provided we are creating the file, not updating it + if f.Resource == nil { + return fragments + } + + // Generate subCommands code fragments + imports := make([]string, 0) + switches := make([]string, 0) + + // add the imports + imports = append(imports, fmt.Sprintf(initImportsFragment, + f.Resource.Version, + strings.ToLower(f.Resource.Kind), + fmt.Sprintf("%s/%s", f.Resource.Path, f.Builder.GetPackageName()), + )) + + // add the switches + switches = append(switches, fmt.Sprintf(initVersionMapFragment, + f.Resource.Version, + fmt.Sprintf("%s%s.Sample(i.RequiredOnly)", + f.Resource.Version, + strings.ToLower(f.Resource.Kind), + )), + ) + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), initImportsMarker)] = imports + } + + if len(switches) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), initVersionMapMarker)] = switches + } + + return fragments +} + +const ( + // cmdInitSub scaffolds the CLI subcommand logic for an individual component. + cmdInitSub = `{{ .Boilerplate }} + +package {{ .Resource.Group }} + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "{{ .Repo }}/apis/{{ .Resource.Group }}" + + cmdinit "{{ .Repo }}/cmd/{{ .RootCmd.Name }}/commands/init" + %s +) + +// get{{ .Resource.Kind }}Manifest returns the sample {{ .Resource.Kind }} manifest +// based upon API Version input. +func get{{ .Resource.Kind }}Manifest(i *cmdinit.InitSubCommand) (string, error) { + apiVersion := i.APIVersion + if apiVersion == "" || apiVersion == "latest" { + return {{ .Resource.Group }}.{{ .Resource.Kind }}LatestSample, nil + } + + // generate a map of all versions to samples for each api version created + manifestMap := map[string]string{ + %s + } + + // return the manifest if it is not blank + manifest := manifestMap[apiVersion] + if manifest != "" { + return manifest, nil + } + + // return an error if we did not find a manifest for an api version + return "", fmt.Errorf("unsupported API Version: " + apiVersion) +} + +// New{{ .Resource.Kind }}SubCommand creates a new command and adds it to its +// parent command. +func New{{ .Resource.Kind }}SubCommand(parentCommand *cobra.Command) { + initCmd := &cmdinit.InitSubCommand{ + Name: "{{ .InitCommandName }}", + Description: "{{ .InitCommandDescr }}", + InitFunc: Init{{ .Resource.Kind }}, + SubCommandOf: parentCommand, + } + + initCmd.Setup() +} + +func Init{{ .Resource.Kind }}(i *cmdinit.InitSubCommand) error { + manifest, err := get{{ .Resource.Kind }}Manifest(i) + if err != nil { + return fmt.Errorf("unable to get manifest for {{ .Resource.Kind }}; %%w", err) + } + + outputStream := os.Stdout + + if _, err := outputStream.WriteString(manifest); err != nil { + return fmt.Errorf("failed to write to stdout, %%w", err) + } + + return nil +} +` +) diff --git a/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_root.go b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_root.go new file mode 100644 index 0000000..d42644d --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_root.go @@ -0,0 +1,271 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package cli + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/workload/v1/commands/companion" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var ( + _ machinery.Template = &CmdRoot{} + _ machinery.Inserter = &CmdRootUpdater{} +) + +// CmdRoot scaffolds the root command file for the companion CLI. +type CmdRoot struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + + // input variables + Initializer kinds.WorkloadBuilder + + // template variables + RootCmd companion.CLI + IsCollection bool +} + +func (f *CmdRoot) SetTemplateDefaults() error { + // set template variables + f.IsCollection = f.Initializer.IsCollection() + f.RootCmd = *f.Initializer.GetRootCommand() + + // set interface variables + f.Path = filepath.Join("cmd", f.RootCmd.Name, "commands", "root.go") + f.TemplateBody = fmt.Sprintf(CmdRootTemplate, + machinery.NewMarkerFor(f.Path, subcommandsImportsMarker), + machinery.NewMarkerFor(f.Path, subcommandsInitMarker), + machinery.NewMarkerFor(f.Path, subcommandsGenerateMarker), + machinery.NewMarkerFor(f.Path, subcommandsVersionMarker), + ) + + return nil +} + +// CmdRootUpdater updates root.go to run sub commands. +type CmdRootUpdater struct { //nolint:maligned + machinery.RepositoryMixin + machinery.MultiGroupMixin + machinery.ResourceMixin + + // input variables + Builder kinds.WorkloadBuilder + InitCommand bool + GenerateCommand bool + VersionCommand bool + + // template variables + RootCmdName string +} + +// GetPath implements file.Builder interface. +func (f *CmdRootUpdater) GetPath() string { + return filepath.Join("cmd", f.Builder.GetRootCommand().Name, "commands", "root.go") +} + +// GetIfExistsAction implements file.Builder interface. +func (*CmdRootUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile +} + +const subcommandsImportsMarker = "operator-builder:subcommands:imports" +const subcommandsInitMarker = "operator-builder:subcommands:init" +const subcommandsGenerateMarker = "operator-builder:subcommands:generate" +const subcommandsVersionMarker = "operator-builder:subcommands:version" + +// GetMarkers implements file.Inserter interface. +func (f *CmdRootUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.GetPath(), subcommandsImportsMarker), + machinery.NewMarkerFor(f.GetPath(), subcommandsInitMarker), + machinery.NewMarkerFor(f.GetPath(), subcommandsGenerateMarker), + machinery.NewMarkerFor(f.GetPath(), subcommandsVersionMarker), + } +} + +// Code Fragments. +const ( + subcommandCodeFragment = `%s%s.New%sSubCommand(parentCommand) +` + importSubCommandCodeFragment = `%s%s "%s" +` +) + +// GetCodeFragments implements file.Inserter interface. +func (f *CmdRootUpdater) 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 { + return fragments + } + + f.RootCmdName = f.Builder.GetRootCommand().Name + + // Generate a command path for imports + commandPath := fmt.Sprintf("%s/cmd/%s/commands", f.Repo, f.RootCmdName) + + // Generate subCommands and imports code fragments + imports := make([]string, 0) + generateCommands := make([]string, 0) + initCommands := make([]string, 0) + versionCommands := make([]string, 0) + + if f.InitCommand { + imports = append(imports, fmt.Sprintf(importSubCommandCodeFragment, + "init", + f.Builder.GetAPIGroup(), + fmt.Sprintf("%s/init/%s", commandPath, f.Builder.GetAPIGroup())), + ) + + initCommands = append(initCommands, fmt.Sprintf(subcommandCodeFragment, + "init", + f.Builder.GetAPIGroup(), + f.Builder.GetAPIKind()), + ) + } + + // scaffold the generate command code fragments unless we have a collection without resources + if (f.Builder.HasChildResources() && f.Builder.IsCollection()) || !f.Builder.IsCollection() { + if f.GenerateCommand { + imports = append(imports, fmt.Sprintf(importSubCommandCodeFragment, + "generate", + f.Builder.GetAPIGroup(), + fmt.Sprintf("%s/generate/%s", commandPath, f.Builder.GetAPIGroup())), + ) + + generateCommands = append(generateCommands, fmt.Sprintf(subcommandCodeFragment, + "generate", + f.Builder.GetAPIGroup(), + f.Builder.GetAPIKind()), + ) + } + } + + if f.VersionCommand { + imports = append(imports, fmt.Sprintf(importSubCommandCodeFragment, + "version", + f.Builder.GetAPIGroup(), + fmt.Sprintf("%s/version/%s", commandPath, f.Builder.GetAPIGroup())), + ) + + versionCommands = append(versionCommands, fmt.Sprintf(subcommandCodeFragment, + "version", + f.Builder.GetAPIGroup(), + f.Builder.GetAPIKind()), + ) + } + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), subcommandsImportsMarker)] = imports + } + + if len(initCommands) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), subcommandsInitMarker)] = initCommands + } + + if len(generateCommands) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), subcommandsGenerateMarker)] = generateCommands + } + + if len(versionCommands) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), subcommandsVersionMarker)] = versionCommands + } + + return fragments +} + +const CmdRootTemplate = `{{ .Boilerplate }} + +package commands + +import ( + "github.com/spf13/cobra" + + // common imports for subcommands + cmdinit "{{ .Repo }}/cmd/{{ .RootCmd.Name }}/commands/init" + cmdgenerate "{{ .Repo }}/cmd/{{ .RootCmd.Name }}/commands/generate" + cmdversion "{{ .Repo }}/cmd/{{ .RootCmd.Name }}/commands/version" + + // specific imports for workloads + %s +) + +// {{ .RootCmd.VarName }}Command represents the base command when called without any subcommands. +type {{ .RootCmd.VarName }}Command struct { + *cobra.Command +} + +// New{{ .RootCmd.VarName }}Command returns an instance of the {{ .RootCmd.VarName }}Command. +func New{{ .RootCmd.VarName }}Command() *{{ .RootCmd.VarName }}Command { + c := &{{ .RootCmd.VarName }}Command{ + Command: &cobra.Command{ + Use: "{{ .RootCmd.Name }}", + Short: "{{ .RootCmd.Description }}", + Long: "{{ .RootCmd.Description }}", + }, + } + + c.addSubCommands() + + return c +} + +// Run represents the main entry point into the command +// This is called by main.main() to execute the root command. +func (c *{{ .RootCmd.VarName }}Command) Run() { + cobra.CheckErr(c.Execute()) +} + +func (c *{{ .RootCmd.VarName }}Command) newInitSubCommand() { + {{- if .IsCollection }} + parentCommand := cmdinit.GetParent(cmdinit.NewBaseInitSubCommand(c.Command)) + {{ else }} + parentCommand := cmdinit.GetParent(c.Command) + {{ end -}} + _ = parentCommand + + // add the init subcommands + %s +} + +func (c *{{ .RootCmd.VarName }}Command) newGenerateSubCommand() { + {{- if .IsCollection }} + parentCommand := cmdgenerate.GetParent(cmdgenerate.NewBaseGenerateSubCommand(c.Command)) + {{ else }} + parentCommand := cmdgenerate.GetParent(c.Command) + {{ end -}} + _ = parentCommand + + // add the generate subcommands + %s +} + +func (c *{{ .RootCmd.VarName }}Command) newVersionSubCommand() { + {{- if .IsCollection }} + parentCommand := cmdversion.GetParent(cmdversion.NewBaseVersionSubCommand(c.Command)) + {{ else }} + parentCommand := cmdversion.GetParent(c.Command) + {{ end -}} + _ = parentCommand + + // add the version subcommands + %s +} + +// addSubCommands adds any additional subCommands to the root command. +func (c *{{ .RootCmd.VarName }}Command) addSubCommands() { + c.newInitSubCommand() + c.newGenerateSubCommand() + c.newVersionSubCommand() +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_version.go b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_version.go new file mode 100644 index 0000000..e37656f --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_version.go @@ -0,0 +1,149 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package cli + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +const ( + versionCommandName = "version" + versionCommandDescr = "display the version information" +) + +var _ machinery.Template = &CmdVersion{} + +// CmdVersion scaffolds the companion CLI's version subcommand for +// component workloads. The version logic will live in the workload's +// subcommand to this command; see cmd_version_sub.go. +type CmdVersion struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + + // input variables + Initializer kinds.WorkloadBuilder + + // template variables + VersionCommandName string + VersionCommandDescr string +} + +func (f *CmdVersion) SetTemplateDefaults() error { + // set template variables + f.VersionCommandName = versionCommandName + f.VersionCommandDescr = versionCommandDescr + + // set interface variables + f.Path = filepath.Join("cmd", f.Initializer.GetRootCommand().Name, "commands", "version", "version.go") + f.TemplateBody = cliCmdVersionTemplate + + return nil +} + +const cliCmdVersionTemplate = `{{ .Boilerplate }} + +package version + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var CLIVersion = "dev" + +type VersionInfo struct { + CLIVersion string ` + "`" + `json:"cliVersion"` + "`" + ` + APIVersions []string ` + "`" + `json:"apiVersions"` + "`" + ` +} + +type VersionFunc func(*VersionSubCommand) error + +type VersionSubCommand struct { + *cobra.Command + + // options + Name string + Description string + SubCommandOf *cobra.Command + + VersionFunc VersionFunc +} + +{{ if .Initializer.IsCollection }} +// NewBaseVersionSubCommand returns a subcommand that is meant to belong to a parent +// subcommand but have subcommands itself. +func NewBaseVersionSubCommand(parentCommand *cobra.Command) *VersionSubCommand { + versionCmd := &VersionSubCommand{ + Name: "{{ .VersionCommandName }}", + Description: "{{ .VersionCommandDescr }}", + SubCommandOf: parentCommand, + } + + versionCmd.Setup() + + return versionCmd +} +{{ end }} + +// Setup sets up this command to be used as a command. +func (v *VersionSubCommand) Setup() { + v.Command = &cobra.Command{ + Use: v.Name, + Short: v.Description, + Long: v.Description, + } + + // run the version function if the function signature is set + if v.VersionFunc != nil { + v.RunE = v.version + } + + // add this as a subcommand of another command if set + if v.SubCommandOf != nil { + v.SubCommandOf.AddCommand(v.Command) + } +} + +// version run the function to display version information about a workload. +func (v *VersionSubCommand) version(cmd *cobra.Command, args []string) error { + return v.VersionFunc(v) +} + +// GetParent is a convenience function written when the CLI code is scaffolded +// to return the parent command and avoid scaffolding code with bad imports. +func GetParent(c interface{}) *cobra.Command { + switch subcommand := c.(type) { + case *VersionSubCommand: + return subcommand.Command + case *cobra.Command: + return subcommand + } + + panic(fmt.Sprintf("subcommand is not proper type: %T", c)) +} + +// Display will parse and print the information stored on the VersionInfo object. +func (v *VersionInfo) Display() error { + output, err := json.Marshal(v) + if err != nil { + return fmt.Errorf("failed to determine versionInfo, %s", err) + } + + outputStream := os.Stdout + + if _, err := outputStream.WriteString(fmt.Sprintln(string(output))); err != nil { + return fmt.Errorf("failed to write to stdout, %s", err) + } + + return nil +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_version_sub.go b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_version_sub.go new file mode 100644 index 0000000..d2889cf --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/cli/cmd_version_sub.go @@ -0,0 +1,181 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package cli + +import ( + "fmt" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/commands/companion" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var ( + _ machinery.Template = &CmdVersionSub{} + _ machinery.Inserter = &CmdVersionSubUpdater{} +) + +// cmdVersionSubCommon include the common fields that are shared by all version +// subcommand structs for templating purposes. +type cmdVersionSubCommon struct { + RootCmd companion.CLI + SubCmd companion.CLI +} + +// CmdVersionSub scaffolds the root command file for the companion CLI. +type CmdVersionSub struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + machinery.RepositoryMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + cmdVersionSubCommon + VersionCommandName string + VersionCommandDescr string +} + +func (f *CmdVersionSub) SetTemplateDefaults() error { + // set template fields + f.RootCmd = *f.Builder.GetRootCommand() + f.SubCmd = *f.Builder.GetSubCommand() + + if f.Builder.IsStandalone() { + f.VersionCommandName = versionCommandName + f.VersionCommandDescr = versionCommandDescr + } else { + f.VersionCommandName = f.SubCmd.Name + f.VersionCommandDescr = f.SubCmd.Description + } + + // set interface fields + f.Path = f.SubCmd.GetSubCmdRelativeFileName( + f.RootCmd.Name, + "version", + f.Resource.Group, + utils.ToFileName(f.Resource.Kind), + ) + + f.TemplateBody = cmdVersionSub + + return nil +} + +// CmdVersionSubUpdater updates a specific components version subcommand with +// appropriate version information. +type CmdVersionSubUpdater struct { //nolint:maligned + machinery.RepositoryMixin + machinery.MultiGroupMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + cmdVersionSubCommon +} + +// GetPath implements file.Builder interface. +func (f *CmdVersionSubUpdater) GetPath() string { + return f.SubCmd.GetSubCmdRelativeFileName( + f.Builder.GetRootCommand().Name, + "version", + f.Resource.Group, + utils.ToFileName(f.Resource.Kind), + ) +} + +// GetIfExistsAction implements file.Builder interface. +func (*CmdVersionSubUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile +} + +const apiVersionsMarker = "operator-builder:apiversions" + +// GetMarkers implements file.Inserter interface. +func (f *CmdVersionSubUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.GetPath(), apiVersionsMarker), + } +} + +// Code Fragments. +const ( + versionCodeFragment = `"%s", +` +) + +// GetCodeFragments implements file.Inserter interface. +func (f *CmdVersionSubUpdater) 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 { + return fragments + } + + // set template fields + f.RootCmd = *f.Builder.GetRootCommand() + f.SubCmd = *f.Builder.GetSubCommand() + + // Generate subCommands code fragments + apiVersions := make([]string, 0) + apiVersions = append(apiVersions, fmt.Sprintf(versionCodeFragment, f.Resource.Version)) + + // Only store code fragments in the map if the slices are non-empty + if len(apiVersions) != 0 { + fragments[machinery.NewMarkerFor(f.GetPath(), apiVersionsMarker)] = apiVersions + } + + return fragments +} + +const ( + cmdVersionSub = `{{ .Boilerplate }} + +package {{ .Resource.Group }} + +import ( + "github.com/spf13/cobra" + + cmdversion "{{ .Repo }}/cmd/{{ .RootCmd.Name }}/commands/version" + + "{{ .Repo }}/apis/{{ .Resource.Group }}" +) + +// New{{ .Resource.Kind }}SubCommand creates a new command and adds it to its +// parent command. +func New{{ .Resource.Kind }}SubCommand(parentCommand *cobra.Command) { + versionCmd := &cmdversion.VersionSubCommand{ + Name: "{{ .VersionCommandName }}", + Description: "{{ .VersionCommandDescr }}", + VersionFunc: Version{{ .Resource.Kind }}, + SubCommandOf: parentCommand, + } + + versionCmd.Setup() +} + +func Version{{ .Resource.Kind }}(v *cmdversion.VersionSubCommand) error { + apiVersions := make([]string, len({{ .Resource.Group }}.{{ .Resource.Kind }}GroupVersions())) + + for i, groupVersion := range {{ .Resource.Group }}.{{ .Resource.Kind }}GroupVersions() { + apiVersions[i] = groupVersion.Version + } + + versionInfo := cmdversion.VersionInfo{ + CLIVersion: cmdversion.CLIVersion, + APIVersions: apiVersions, + } + + return versionInfo.Display() +} +` +) diff --git a/internal/plugins/workload/v2/scaffolds/templates/cli/main.go b/internal/plugins/workload/v2/scaffolds/templates/cli/main.go new file mode 100644 index 0000000..ec9d550 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/cli/main.go @@ -0,0 +1,54 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package cli + +import ( + "path/filepath" + "text/template" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/commands/companion" +) + +var _ machinery.Template = &Main{} + +// Main scaffolds the main package for the companion CLI. +type Main struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + + RootCmd companion.CLI +} + +func (f *Main) SetTemplateDefaults() error { + // set interface variables + f.Path = filepath.Join("cmd", f.RootCmd.Name, "main.go") + f.TemplateBody = cliMainTemplate + + f.IfExistsAction = machinery.SkipFile + + return nil +} + +func (*Main) GetFuncMap() template.FuncMap { + return utils.RemoveStringHelper() +} + +const cliMainTemplate = `{{ .Boilerplate }} + +package main + +import ( + "{{ .Repo }}/cmd/{{ .RootCmd.Name }}/commands" +) + +func main() { + {{ .RootCmd.Name | removeString "-" }} := commands.New{{ .RootCmd.VarName }}Command() + {{ .RootCmd.Name | removeString "-" }}.Run() +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/config/crd/kustomization.go b/internal/plugins/workload/v2/scaffolds/templates/config/crd/kustomization.go new file mode 100644 index 0000000..6b4d380 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/config/crd/kustomization.go @@ -0,0 +1,117 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package crd + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var ( + _ machinery.Template = &Kustomization{} + _ machinery.Inserter = &Kustomization{} +) + +// Kustomization scaffolds a file that defines the kustomization scheme for the crd folder. +type Kustomization struct { + machinery.TemplateMixin + machinery.ResourceMixin +} + +// SetTemplateDefaults implements file.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, + machinery.NewMarkerFor(f.Path, resourceMarker), + machinery.NewMarkerFor(f.Path, webhookPatchMarker), + machinery.NewMarkerFor(f.Path, caInjectionPatchMarker), + ) + + return nil +} + +const ( + resourceMarker = "crdkustomizeresource" + webhookPatchMarker = "crdkustomizewebhookpatch" + caInjectionPatchMarker = "crdkustomizecainjectionpatch" +) + +// GetMarkers implements file.Inserter. +func (f *Kustomization) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, resourceMarker), + machinery.NewMarkerFor(f.Path, webhookPatchMarker), + machinery.NewMarkerFor(f.Path, caInjectionPatchMarker), + } +} + +const ( + resourceCodeFragment = `- bases/%s_%s.yaml +` + webhookPatchCodeFragment = `#- patches/webhook_in_%s.yaml +` + caInjectionPatchCodeFragment = `#- patches/cainjection_in_%s.yaml +` +) + +// GetCodeFragments implements file.Inserter. +func (f *Kustomization) GetCodeFragments() machinery.CodeFragmentsMap { + const codeFragmentsLen = 3 + fragments := make(machinery.CodeFragmentsMap, codeFragmentsLen) + + // Generate resource code fragments + res := make([]string, 0) + res = append(res, fmt.Sprintf(resourceCodeFragment, f.Resource.QualifiedGroup(), f.Resource.Plural)) + + // Generate resource code fragments + webhookPatch := make([]string, 0) + webhookPatch = append(webhookPatch, fmt.Sprintf(webhookPatchCodeFragment, f.Resource.Plural)) + + // Generate resource code fragments + caInjectionPatch := make([]string, 0) + caInjectionPatch = append(caInjectionPatch, fmt.Sprintf(caInjectionPatchCodeFragment, f.Resource.Plural)) + + // Only store code fragments in the map if the slices are non-empty + if len(res) != 0 { + fragments[machinery.NewMarkerFor(f.Path, resourceMarker)] = res + } + + if len(webhookPatch) != 0 { + fragments[machinery.NewMarkerFor(f.Path, webhookPatchMarker)] = webhookPatch + } + + if len(caInjectionPatch) != 0 { + fragments[machinery.NewMarkerFor(f.Path, caInjectionPatchMarker)] = caInjectionPatch + } + + return fragments +} + +const kustomizationTemplate = `# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +%s + +patchesStrategicMerge: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +%s + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +%s + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/config/manifests/kustomization.go b/internal/plugins/workload/v2/scaffolds/templates/config/manifests/kustomization.go new file mode 100644 index 0000000..3e759fe --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/config/manifests/kustomization.go @@ -0,0 +1,97 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +// NOTE: this was copied from operator-sdk in order to +// include support for OpenShift Lifecycle Manager. It was +// copied because operator-sdk templates are internal to the +// repo and unable to be imported directly. Including original +// license with this file. + +// 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 manifests + +import ( + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &Kustomization{} + +// Kustomization scaffolds a kustomization.yaml for the manifests overlay folder. +type Kustomization struct { + machinery.TemplateMixin + machinery.ProjectNameMixin + + // SupportsKustomizeV4 is true for the projects that are + // scaffold using the kustomize/v2-aplha plugin and + // the major bump for it 4x + // Previous versions uses 3x + SupportsKustomizeV4 bool + + SupportsWebhooks bool +} + +// SetTemplateDefaults implements machinery.Template. +func (f *Kustomization) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "manifests", "kustomization.yaml") + } + + // We cannot overwiting the file after it be created because + // it might contain user changes (i.e to work with Kustomize 4.x + // the target /spec/template/spec/containers/1/volumeMounts/0 + // needs to be replaced with /spec/template/spec/containers/0/volumeMounts/0 + f.IfExistsAction = machinery.SkipFile + + f.TemplateBody = kustomizationTemplate + + return nil +} + +const kustomizationTemplate = `# These resources constitute the fully configured set of manifests +# used to generate the 'manifests/' directory in a bundle. +resources: +- bases/{{ .ProjectName }}.clusterserviceversion.yaml +- ../default +- ../samples +- ../scorecard +{{ if .SupportsWebhooks }} +# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. +# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. +# These patches remove the unnecessary "cert" volume and its manager container volumeMount. +#patchesJson6902: +#- target: +# group: apps +# version: v1 +# kind: Deployment +# name: controller-manager +# namespace: system +# patch: |- +# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. +# - op: remove +{{ if .SupportsKustomizeV4 }} +# path: /spec/template/spec/containers/0/volumeMounts/0 +{{ else -}} +# path: /spec/template/spec/containers/1/volumeMounts/0 +{{ end -}} +# # Remove the "cert" volume, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing volumes in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/volumes/0 +{{ end -}} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/config/samples/crd_sample.go b/internal/plugins/workload/v2/scaffolds/templates/config/samples/crd_sample.go new file mode 100644 index 0000000..9947567 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/config/samples/crd_sample.go @@ -0,0 +1,65 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package samples + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var _ machinery.Template = &CRDSample{} + +// CRDSample scaffolds a file that defines a sample manifest for the CRD. +type CRDSample struct { + machinery.TemplateMixin + machinery.ResourceMixin + + SpecFields *kinds.APIFields + IsClusterScoped bool + RequiredOnly bool +} + +func (f *CRDSample) SetTemplateDefaults() error { + f.Path = filepath.Join( + "config", + "samples", + fmt.Sprintf( + "%s_%s_%s.yaml", + f.Resource.Group, + f.Resource.Version, + utils.ToFileName(f.Resource.Kind)), + ) + + f.RequiredOnly = false + f.TemplateBody = SampleTemplate + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +const SampleTemplate = `apiVersion: {{ .Resource.QualifiedGroup }}/{{ .Resource.Version }} +kind: {{ .Resource.Kind }} +metadata: + name: {{ lower .Resource.Kind }}-sample +{{- if not .IsClusterScoped }} + namespace: default +{{- end }} +{{ .SpecFields.GenerateSampleSpec false -}} +` + +const SampleTemplateRequiredOnly = `apiVersion: {{ .Resource.QualifiedGroup }}/{{ .Resource.Version }} +kind: {{ .Resource.Kind }} +metadata: + name: {{ lower .Resource.Kind }}-sample +{{- if not .IsClusterScoped }} + namespace: default +{{- end }} +{{ .SpecFields.GenerateSampleSpec true -}} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/config/samples/kustomization.go b/internal/plugins/workload/v2/scaffolds/templates/config/samples/kustomization.go new file mode 100644 index 0000000..74720a4 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/config/samples/kustomization.go @@ -0,0 +1,84 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +// NOTE: this was copied from operator-sdk in order to +// include support for OpenShift Lifecycle Manager. It was +// copied because operator-sdk templates are internal to the +// repo and unable to be imported directly. Including original +// license with this file. + +// 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 samples + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var ( + _ machinery.Template = &Kustomization{} + _ machinery.Inserter = &Kustomization{} +) + +// Kustomization scaffolds a kustomization.yaml for the manifests overlay folder. +type Kustomization struct { + machinery.TemplateMixin + machinery.ResourceMixin +} + +// SetTemplateDefaults implements machinery.Template. +func (f *Kustomization) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join("config", "samples", "kustomization.yaml") + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + f.TemplateBody = fmt.Sprintf(kustomizationTemplate, machinery.NewMarkerFor(f.Path, samplesMarker)) + + return nil +} + +const ( + samplesMarker = "manifestskustomizesamples" +) + +// GetMarkers implements file.Inserter. +func (f *Kustomization) GetMarkers() []machinery.Marker { + return []machinery.Marker{machinery.NewMarkerFor(f.Path, samplesMarker)} +} + +const samplesCodeFragment = `- %s +` + +// makeCRFileName returns a Custom Resource example file name in the same format +// as kubebuilder's CreateAPI plugin for a gvk. +func (f *Kustomization) makeCRFileName() string { + return f.Resource.Replacer().Replace("%[group]_%[version]_%[kind].yaml") +} + +// GetCodeFragments implements file.Inserter. +func (f *Kustomization) GetCodeFragments() machinery.CodeFragmentsMap { + return machinery.CodeFragmentsMap{ + machinery.NewMarkerFor(f.Path, samplesMarker): []string{fmt.Sprintf(samplesCodeFragment, f.makeCRFileName())}, + } +} + +const kustomizationTemplate = `## Append samples you want in your CSV to this file as resources ## +resources: +%s +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/config/scorecard/scorecard.go b/internal/plugins/workload/v2/scaffolds/templates/config/scorecard/scorecard.go new file mode 100644 index 0000000..3c20c15 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/config/scorecard/scorecard.go @@ -0,0 +1,195 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +// NOTE: this was copied from operator-sdk in order to +// include support for OpenShift Lifecycle Manager. It was +// copied because operator-sdk templates are internal to the +// repo and unable to be imported directly. Including original +// license with this file. + +// 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 scorecard + +import ( + "errors" + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &Scorecard{} + +var ( + ErrUnknwonScorecardType = errors.New("unknown scorecard type") +) + +//nolint:golint +type ScorecardType int + +const ( + kustomizeFileSubPath = "kustomization.yaml" + operatorSdkImageVersion = "v1.28.0" + + ScorecardTypeUnknown ScorecardType = iota + ScorecardTypeBase + ScorecardTypeKustomize + ScorecardTypePatchesOLM + ScorecardTypePatchesBasic +) + +// Scorecard scaffolds a file which represents an Operator Lifecycle Manager scorecard. +// It is only used when --enable-olm is set to true. +type Scorecard struct { + machinery.TemplateMixin + + // input variables + ScorecardTestImage string + ScorecardType ScorecardType +} + +func (f *Scorecard) SetTemplateDefaults() error { + if f.ScorecardType == ScorecardTypeUnknown { + return ErrUnknwonScorecardType + } + + f.Path = filepath.Join(append([]string{"config", "scorecard"}, getScorecardSubPath(f.ScorecardType)...)...) + f.TemplateBody = getScorecardTemplate(f.ScorecardType) + + f.ScorecardTestImage = fmt.Sprintf("quay.io/operator-framework/scorecard-test:%s", operatorSdkImageVersion) + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +const ( + // kustomizationFile is a kustomization.yaml file for the scorecard componentconfig. + // This should always be written to config/scorecard/kustomization.yaml. + kustomizeFile = `resources: + - bases/config.yaml +patchesJson6902: + - path: patches/basic.config.yaml + target: + group: scorecard.operatorframework.io + version: v1alpha3 + kind: Configuration + name: config + - path: patches/olm.config.yaml + target: + group: scorecard.operatorframework.io + version: v1alpha3 + kind: Configuration + name: config +%[1]s +` + + // YAML file marker to append to kustomization.yaml files. + patchesJSON6902Marker = "patchesJson6902" + + // Config is an empty scorecard componentconfig with parallel stages. + config = `apiVersion: scorecard.operatorframework.io/v1alpha3 +kind: Configuration +metadata: + name: config +stages: + - parallel: true + tests: [] +` + + // PatchBasic contains all default "basic" test configurations. + patchBasic = `- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - basic-check-spec + image: {{ .ScorecardTestImage }} + labels: + suite: basic + test: basic-check-spec-test +` + + // PatchOLM contains all default "olm" test configurations. + patchOLM = `- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-bundle-validation + image: {{ .ScorecardTestImage }} + labels: + suite: olm + test: olm-bundle-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-validation + image: {{ .ScorecardTestImage }} + labels: + suite: olm + test: olm-crds-have-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-resources + image: {{ .ScorecardTestImage }} + labels: + suite: olm + test: olm-crds-have-resources-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-spec-descriptors + image: {{ .ScorecardTestImage }} + labels: + suite: olm + test: olm-spec-descriptors-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-status-descriptors + image: {{ .ScorecardTestImage }} + labels: + suite: olm + test: olm-status-descriptors-test +` +) + +func getScorecardSubPath(scorecardType ScorecardType) []string { + return map[ScorecardType][]string{ + ScorecardTypeBase: {"bases", "config.yaml"}, + ScorecardTypeKustomize: {kustomizeFileSubPath}, + ScorecardTypePatchesBasic: {"patches", "basic.config.yaml"}, + ScorecardTypePatchesOLM: {"patches", "olm.config.yaml"}, + }[scorecardType] +} + +func getScorecardTemplate(scorecardType ScorecardType) string { + return map[ScorecardType]string{ + ScorecardTypeBase: config, + ScorecardTypeKustomize: fmt.Sprintf(kustomizeFile, machinery.NewMarkerFor(kustomizeFileSubPath, patchesJSON6902Marker)), + ScorecardTypePatchesBasic: patchBasic, + ScorecardTypePatchesOLM: patchOLM, + }[scorecardType] +} diff --git a/internal/plugins/workload/v2/scaffolds/templates/controller/controller.go b/internal/plugins/workload/v2/scaffolds/templates/controller/controller.go new file mode 100644 index 0000000..d80d48b --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/controller/controller.go @@ -0,0 +1,424 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package controller + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +var _ machinery.Template = &Controller{} + +// Controller scaffolds the workload's controller. +type Controller struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + BaseImports []string + OtherImports []string + InternalImports []string +} + +func (f *Controller) SetTemplateDefaults() error { + f.Path = filepath.Join( + "controllers", + f.Resource.Group, + fmt.Sprintf("%s_controller.go", utils.ToFileName(f.Resource.Kind)), + ) + + f.TemplateBody = controllerTemplate + f.IfExistsAction = machinery.OverwriteFile + + f.setBaseImports() + f.setOtherImports() + f.setInternalImports() + + return nil +} + +func (f *Controller) setBaseImports() { + f.BaseImports = []string{`"context"`, `"fmt"`} + + if f.Builder.IsComponent() { + f.BaseImports = append(f.BaseImports, `"errors"`, `"reflect"`) + } +} + +func (f *Controller) setOtherImports() { + f.OtherImports = []string{ + `"github.com/go-logr/logr"`, + `apierrs "k8s.io/apimachinery/pkg/api/errors"`, + `"k8s.io/client-go/tools/record"`, + `ctrl "sigs.k8s.io/controller-runtime"`, + `"sigs.k8s.io/controller-runtime/pkg/client"`, + `"sigs.k8s.io/controller-runtime/pkg/controller"`, + `"sigs.k8s.io/controller-runtime/pkg/manager"`, + `"github.com/nukleros/operator-builder-tools/pkg/controller/phases"`, + `"github.com/nukleros/operator-builder-tools/pkg/controller/predicates"`, + `"github.com/nukleros/operator-builder-tools/pkg/controller/workload"`, + } + + if f.Builder.IsComponent() { + f.OtherImports = append(f.OtherImports, + `"github.com/nukleros/operator-builder-tools/pkg/resources"`, + `"sigs.k8s.io/controller-runtime/pkg/event"`, + `"sigs.k8s.io/controller-runtime/pkg/handler"`, + `"sigs.k8s.io/controller-runtime/pkg/predicate"`, + `"sigs.k8s.io/controller-runtime/pkg/reconcile"`, + `"sigs.k8s.io/controller-runtime/pkg/source"`, + `"k8s.io/apimachinery/pkg/types"`, + ) + } +} + +func (f *Controller) setInternalImports() { + f.InternalImports = []string{ + fmt.Sprintf(`"%s/internal/dependencies"`, f.Repo), + fmt.Sprintf(`"%s/internal/mutate"`, f.Repo), + fmt.Sprintf(`%s %q`, f.Resource.ImportAlias(), f.Resource.Path), + } + + if f.Builder.IsComponent() { + f.InternalImports = append(f.InternalImports, f.getAPITypesPath(f.Builder.GetCollection())) + } + + if f.Builder.HasChildResources() { + f.InternalImports = append(f.InternalImports, + fmt.Sprintf(`"%s/%s"`, + f.Resource.Path, + f.Builder.GetPackageName(), + ), + ) + } +} + +func (f *Controller) getAPITypesPath(builder kinds.WorkloadBuilder) string { + return fmt.Sprintf(`%s%s "%s/apis/%s/%s"`, + builder.GetAPIGroup(), + builder.GetAPIVersion(), + f.Repo, + builder.GetAPIGroup(), + builder.GetAPIVersion(), + ) +} + +//nolint:lll +const controllerTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Group }} + +import ( + {{ range .BaseImports -}} + {{ . }} + {{ end }} + + {{ range .OtherImports -}} + {{ . }} + {{ end }} + + {{ range .InternalImports -}} + {{ . }} + {{ end }} +) + +// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object. +type {{ .Resource.Kind }}Reconciler struct { + client.Client + Name string + Log logr.Logger + Controller controller.Controller + Events record.EventRecorder + FieldManager string + Watches []client.Object + Phases *phases.Registry + Manager manager.Manager +} + +func New{{ .Resource.Kind }}Reconciler(mgr ctrl.Manager) *{{ .Resource.Kind }}Reconciler { + return &{{ .Resource.Kind }}Reconciler{ + Name: "{{ .Resource.Kind }}", + Client: mgr.GetClient(), + Events: mgr.GetEventRecorderFor("{{ .Resource.Kind }}-Controller"), + FieldManager: "{{ .Resource.Kind }}-reconciler", + Log: ctrl.Log.WithName("controllers").WithName("{{ .Resource.Group }}").WithName("{{ .Resource.Kind }}"), + Watches: []client.Object{}, + Phases: &phases.Registry{}, + Manager: mgr, + } +} + +{{ range .Builder.GetRBACRules -}} +{{ .ToMarker }} +{{ end }} + +// +kubebuilder:rbac:groups=core,resources=namespaces,verbs=list;watch +// +kubebuilder:rbac:groups=core,resources=events,verbs=get;create;update;patch + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.2/pkg/reconcile +func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) { + req, err := r.NewRequest(ctx, request) + if err != nil { + {{- if .Builder.IsComponent }} + if errors.Is(err, workload.ErrCollectionNotFound) { + return ctrl.Result{Requeue: true}, nil + } + {{- end }} + + if !apierrs.IsNotFound(err) { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil + } + + if err := phases.RegisterDeleteHooks(r, req); err != nil { + return ctrl.Result{}, err + } + + // execute the phases + return r.Phases.HandleExecution(r, req) +} + +func (r *{{ .Resource.Kind }}Reconciler) NewRequest(ctx context.Context, request ctrl.Request) (*workload.Request, error) { + component := &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{} + + log := r.Log.WithValues( + "kind", component.GetWorkloadGVK().Kind, + "name", request.Name, + "namespace", request.Namespace, + ) + + // get the component from the cluster + if err := r.Get(ctx, request.NamespacedName, component); err != nil { + if !apierrs.IsNotFound(err) { + log.Error(err, "unable to fetch workload") + + return nil, fmt.Errorf("unable to fetch workload, %w", err) + } + + return nil, err + } + + // create the workload request + workloadRequest := &workload.Request{ + Context: ctx, + Workload: component, + Log: log, + } + + {{ if .Builder.IsComponent }} + // store the collection and return any resulting error + return workloadRequest, r.SetCollection(component, workloadRequest) + {{- else }} + return workloadRequest, nil + {{- end }} +} + +{{- if .Builder.IsComponent }} +// SetCollection sets the collection for a particular workload request. +func (r *{{ .Resource.Kind }}Reconciler) SetCollection(component *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}, req *workload.Request) error { + collection, err := r.GetCollection(component, req) + if err != nil || collection == nil { + return fmt.Errorf("unable to set collection, %w", err) + } + + req.Collection = collection + + return r.EnqueueRequestOnCollectionChange(req) +} + +// GetCollection gets a collection for a component given a list. +func (r *{{ .Resource.Kind }}Reconciler) GetCollection( + component *{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}, + req *workload.Request, +) (*{{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }}, error) { + var collectionList {{ .Builder.GetCollection.Spec.API.Group }}{{ .Builder.GetCollection.Spec.API.Version }}.{{ .Builder.GetCollection.Spec.API.Kind }}List + + if err := r.List(req.Context, &collectionList); err != nil { + return nil, fmt.Errorf("unable to list collection {{ .Builder.GetCollection.Spec.API.Kind }}, %w", err) + } + + // determine if we have requested a specific collection + name, namespace := component.Spec.Collection.Name, component.Spec.Collection.Namespace + + var collectionRef {{ .Resource.ImportAlias }}.{{ .Resource.Kind }}CollectionSpec + + hasSpecificCollection := component.Spec.Collection != collectionRef && component.Spec.Collection.Name != "" + + // if a specific collection has not been requested, we ensure only one exists + if !hasSpecificCollection { + if len(collectionList.Items) != 1 { + return nil, fmt.Errorf("expected only 1 {{ .Builder.GetCollection.Spec.API.Kind }} collection, found %v", len(collectionList.Items)) + } + + return &collectionList.Items[0], nil + } + + // find the collection that was requested and return it + for _, collection := range collectionList.Items { + if collection.Name == name && collection.Namespace == namespace { + return &collection, nil + } + } + + return nil, workload.ErrCollectionNotFound +} + +// EnqueueRequestOnCollectionChange enqueues a reconcile request when an associated collection object changes. +func (r *{{ .Resource.Kind }}Reconciler) EnqueueRequestOnCollectionChange(req *workload.Request) error { + if len(r.Watches) > 0 { + for _, watched := range r.Watches { + if reflect.DeepEqual( + req.Collection.GetObjectKind().GroupVersionKind(), + watched.GetObjectKind().GroupVersionKind(), + ) { + return nil + } + } + } + + // create a function which maps this specific reconcile request + mapFn := func(_ context.Context, collection client.Object) []reconcile.Request { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: req.Workload.GetName(), + Namespace: req.Workload.GetNamespace(), + }, + }, + } + } + + // watch the collection and use our map function to enqueue the request + if err := r.Controller.Watch( + source.Kind(r.Manager.GetCache(), req.Collection), + handler.EnqueueRequestsFromMapFunc(mapFn), + predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + if !resources.EqualNamespaceName(e.ObjectNew, req.Collection) { + return false + } + + return e.ObjectNew != e.ObjectOld + }, + CreateFunc: func(e event.CreateEvent) bool { + return false + }, + GenericFunc: func(e event.GenericEvent) bool { + return false + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return false + }, + }, + ); err != nil { + return err + } + + r.Watches = append(r.Watches, req.Collection) + + return nil +} +{{- end }} + +// GetResources resources runs the methods to properly construct the resources in memory. +func (r *{{ .Resource.Kind }}Reconciler) GetResources(req *workload.Request) ([]client.Object, error) { + {{- if .Builder.HasChildResources }} + component, {{ if .Builder.IsComponent }}collection,{{ end }} err := {{ .Builder.GetPackageName }}.ConvertWorkload(req.Workload{{ if .Builder.IsComponent }}, req.Collection{{ end }}) + if err != nil { + return nil, err + } + + return {{ .Builder.GetPackageName }}.Generate(*component{{ if .Builder.IsComponent }}, *collection{{ end }}, r, req) +{{- else -}} + return []client.Object{}, nil +{{ end -}} +} + +// GetEventRecorder returns the event recorder for writing kubernetes events. +func (r *{{ .Resource.Kind }}Reconciler) GetEventRecorder() record.EventRecorder { + return r.Events +} + +// GetFieldManager returns the name of the field manager for the controller. +func (r *{{ .Resource.Kind }}Reconciler) GetFieldManager() string { + return r.FieldManager +} + +// GetLogger returns the logger from the reconciler. +func (r *{{ .Resource.Kind }}Reconciler) GetLogger() logr.Logger { + return r.Log +} + +// GetName returns the name of the reconciler. +func (r *{{ .Resource.Kind }}Reconciler) GetName() string { + return r.Name +} + +// GetController returns the controller object associated with the reconciler. +func (r *{{ .Resource.Kind }}Reconciler) GetController() controller.Controller { + return r.Controller +} + +// GetManager returns the manager object assocated with the reconciler. +func (r *{{ .Resource.Kind }}Reconciler) GetManager() manager.Manager { + return r.Manager +} + +// GetWatches returns the objects which are current being watched by the reconciler. +func (r *{{ .Resource.Kind }}Reconciler) GetWatches() []client.Object { + return r.Watches +} + +// SetWatch appends a watch to the list of currently watched objects. +func (r *{{ .Resource.Kind }}Reconciler) SetWatch(watch client.Object) { + r.Watches = append(r.Watches, watch) +} + +// CheckReady will return whether a component is ready. +func (r *{{ .Resource.Kind }}Reconciler) CheckReady(req *workload.Request) (bool, error) { + return dependencies.{{ .Resource.Kind }}CheckReady(r, req) +} + +// Mutate will run the mutate function for the workload. +// WARN: this will be deprecated in the future. See apis/group/version/kind/mutate* +func (r *{{ .Resource.Kind }}Reconciler) Mutate( + req *workload.Request, + object client.Object, +) ([]client.Object, bool, error) { + return mutate.{{ .Resource.Kind }}Mutate(r, req, object) +} + +func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { + r.InitializePhases() + + baseController, err := ctrl.NewControllerManagedBy(mgr). + WithEventFilter(predicates.WorkloadPredicates()). + For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}). + Build(r) + if err != nil { + return fmt.Errorf("unable to setup controller, %w", err) + } + + r.Controller = baseController + + return nil +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/controller/controller_suitetest.go b/internal/plugins/workload/v2/scaffolds/templates/controller/controller_suitetest.go new file mode 100644 index 0000000..49e5a9d --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/controller/controller_suitetest.go @@ -0,0 +1,193 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +/* +Copyright 2022 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 controller + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &SuiteTest{} +var _ machinery.Inserter = &SuiteTest{} + +// SuiteTest scaffolds the file that sets up the controller tests +// +//nolint:maligned +type SuiteTest struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + // CRDDirectoryRelativePath define the Path for the CRD + CRDDirectoryRelativePath string +} + +// SetTemplateDefaults implements file.Template. +func (f *SuiteTest) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("controllers", "%[group]", "suite_test.go") + } else { + f.Path = filepath.Join("controllers", "suite_test.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + + f.TemplateBody = fmt.Sprintf(controllerSuiteTestTemplate, + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + ) + + // If is multigroup the path needs to be ../../ since it has + // the group dir. + f.CRDDirectoryRelativePath = `"..",".."` + if f.MultiGroup && f.Resource.Group != "" { + f.CRDDirectoryRelativePath = `"..", "..",".."` + } + + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +const ( + importMarker = "imports" + addSchemeMarker = "scheme" +) + +// GetMarkers implements file.Inserter. +func (f *SuiteTest) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + } +} + +const ( + apiImportCodeFragment = `%s "%s" +` + addschemeCodeFragment = `err = %s.AddToScheme(scheme.Scheme) +Expect(err).NotTo(HaveOccurred()) + +` +) + +// GetCodeFragments implements file.Inserter. +func (f *SuiteTest) GetCodeFragments() machinery.CodeFragmentsMap { + //nolint:gomnd + fragments := make(machinery.CodeFragmentsMap, 2) + + // Generate import code fragments + imports := make([]string, 0) + if f.Resource.Path != "" { + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) + } + + // Generate add scheme code fragments + addScheme := make([]string, 0) + if f.Resource.Path != "" { + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) + } + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports + } + if len(addScheme) != 0 { + fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme + } + + return fragments +} + +const controllerSuiteTestTemplate = `{{ .Boilerplate }} + +{{if and .MultiGroup .Resource.Group }} +package {{ .Resource.PackageName }} +{{else}} +package controller +{{end}} + +import ( + "fmt" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + %s +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join({{ .CRDDirectoryRelativePath }}, "config", "crd", "bases")}, + ErrorIfCRDPathMissing: {{ .Resource.HasAPI }}, + } + + var err error + + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + %s + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/controller/phases.go b/internal/plugins/workload/v2/scaffolds/templates/controller/phases.go new file mode 100644 index 0000000..3cf9edf --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/controller/phases.go @@ -0,0 +1,116 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package controller + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" +) + +var _ machinery.Template = &Controller{} + +// Controller scaffolds the workload's controller. +type Phases struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin + + PackageName string +} + +func (f *Phases) SetTemplateDefaults() error { + f.Path = filepath.Join( + "controllers", + f.Resource.Group, + fmt.Sprintf("%s_phases.go", utils.ToFileName(f.Resource.Kind)), + ) + + f.TemplateBody = phasesTemplate + f.IfExistsAction = machinery.SkipFile + + return nil +} + +const phasesTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Group }} + +import ( + "time" + + "github.com/nukleros/operator-builder-tools/pkg/controller/phases" + ctrl "sigs.k8s.io/controller-runtime" +) + +// InitializePhases defines what phases should be run for each event loop. phases are executed +// in the order they are listed. +func (r *{{ .Resource.Kind }}Reconciler) InitializePhases() { + // Create Phases + r.Phases.Register( + "Dependency", + phases.DependencyPhase, + phases.CreateEvent, + phases.WithCustomRequeueResult(ctrl.Result{RequeueAfter: 5 * time.Second }), + ) + + r.Phases.Register( + "Create-Resources", + phases.CreateResourcesPhase, + phases.CreateEvent, + ) + + r.Phases.Register( + "Check-Ready", + phases.CheckReadyPhase, + phases.CreateEvent, + phases.WithCustomRequeueResult(ctrl.Result{RequeueAfter: 5 * time.Second }), + ) + + r.Phases.Register( + "Complete", + phases.CompletePhase, + phases.CreateEvent, + ) + + // Update Phases + r.Phases.Register( + "Dependency", + phases.DependencyPhase, + phases.UpdateEvent, + phases.WithCustomRequeueResult(ctrl.Result{RequeueAfter: 5 * time.Second }), + ) + + r.Phases.Register( + "Create-Resources", + phases.CreateResourcesPhase, + phases.UpdateEvent, + ) + + r.Phases.Register( + "Check-Ready", + phases.CheckReadyPhase, + phases.UpdateEvent, + phases.WithCustomRequeueResult(ctrl.Result{RequeueAfter: 5 * time.Second }), + ) + + r.Phases.Register( + "Complete", + phases.CompletePhase, + phases.UpdateEvent, + ) + + // Delete Phases + r.Phases.Register( + "DeletionComplete", + phases.DeletionCompletePhase, + phases.DeleteEvent, + ) +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/dockerfile.go b/internal/plugins/workload/v2/scaffolds/templates/dockerfile.go new file mode 100644 index 0000000..4140c4b --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/dockerfile.go @@ -0,0 +1,67 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" +) + +const ( + defaultDockerfilePath = "Dockerfile" +) + +var _ machinery.Template = &Dockerfile{} + +// Dockerfile scaffolds a file that defines the containerized build process. +type Dockerfile struct { + machinery.TemplateMixin + + GoVersion string +} + +// SetTemplateDefaults implements file.Template. +func (f *Dockerfile) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = defaultDockerfilePath + } + + f.GoVersion = utils.GeneratedGoVersionPreferred + f.IfExistsAction = machinery.OverwriteFile + f.TemplateBody = dockerfileTemplate + + return nil +} + +const dockerfileTemplate = `# Build the manager binary +FROM golang:{{ .GoVersion }} as builder + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY main.go main.go +COPY apis/ apis/ +COPY controllers/ controllers/ +COPY internal/ internal/ + +# Build +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/manager . +USER 65532:65532 + +ENTRYPOINT ["/manager"] +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/gomod.go b/internal/plugins/workload/v2/scaffolds/templates/gomod.go new file mode 100644 index 0000000..62bc2cf --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/gomod.go @@ -0,0 +1,87 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" +) + +var _ machinery.Template = &GoMod{} + +// GoMod scaffolds a file that defines the project dependencies. +type GoMod struct { + machinery.TemplateMixin + machinery.RepositoryMixin + + GoVersionMinimum string + Dependencies map[string]string + IndirectDependencies map[string]string +} + +// goModDependencyMap pins the versions within the go.mod file so that they +// do not get auto-updated. +// +// See https://github.com/vmware-tanzu-labs/operator-builder/issues/250 +func goModDependencyMap() map[string]string { + return map[string]string{ + "github.com/go-logr/logr": "v1.4.1", + "github.com/nukleros/operator-builder-tools": "v0.6.1", + "github.com/onsi/ginkgo/v2": "v2.17.1", + "github.com/onsi/gomega": "v1.32.0", + "github.com/spf13/cobra": "v1.8.0", + "github.com/stretchr/testify": "v1.9.0", + "gopkg.in/yaml.v2": "v2.4.0", + "k8s.io/api": "v0.29.4", + "k8s.io/apimachinery": "v0.29.4", + "k8s.io/client-go": "v0.29.4", + "sigs.k8s.io/kubebuilder/v4": "v4.0.0", + "sigs.k8s.io/yaml": "v1.4.0", + + // externally versioned packages via the utils package + "sigs.k8s.io/controller-runtime": utils.ControllerRuntimeVersion, + } +} + +// NOTE: there are no indirect dependencies to manage at this time, but we will leave +// it in place when the time comes to use it. +func goModIndirectDependencyMap() map[string]string { + return map[string]string{} +} + +func (f *GoMod) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "go.mod" + } + + f.GoVersionMinimum = utils.GeneratedGoVersionMinimum + f.Dependencies = goModDependencyMap() + f.IndirectDependencies = goModIndirectDependencyMap() + f.TemplateBody = goModTemplate + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +const goModTemplate = ` +module {{ .Repo }} + +go {{ .GoVersionMinimum }} + +require ( + {{ range $package, $version := $.Dependencies }} + "{{ $package }}" {{ $version }} + {{- end }} +) + +{{ if gt (len $.IndirectDependencies) 0 }} +require ( + {{ range $package, $version := $.IndirectDependencies }} + "{{ $package }}" {{ $version }} + {{- end }} +) +{{- end }} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/hack/boilerplate.go b/internal/plugins/workload/v2/scaffolds/templates/hack/boilerplate.go new file mode 100644 index 0000000..0a05b56 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/hack/boilerplate.go @@ -0,0 +1,135 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +/* +Copyright 2022 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 hack + +import ( + "errors" + "fmt" + "time" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +// DefaultBoilerplatePath is the default path to the boilerplate file. +const DefaultBoilerplatePath = "hack/boilerplate.go.txt" + +var _ machinery.Template = &Boilerplate{} + +var ( + ErrUnknownLicense = errors.New("unknown specified license") +) + +// Boilerplate scaffolds a file that defines the common header for the rest of the files. +type Boilerplate struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + + // License is the License type to write + License string + + // Licenses maps License types to their actual string + Licenses map[string]string + + // Owner is the copyright owner - e.g. "The Kubernetes Authors" + Owner string + + // Year is the copyright year + Year string +} + +// Validate implements file.RequiresValidation. +func (f *Boilerplate) Validate() error { + if f.License == "" { + // A default license will be set later + } else if _, found := knownLicenses()[f.License]; found { + // One of the know licenses + } else if _, found := f.Licenses[f.License]; found { + // A map containing the requested license was also provided + } else { + return fmt.Errorf("license %s, %w", f.License, ErrUnknownLicense) + } + + return nil +} + +// SetTemplateDefaults implements file.Template. +func (f *Boilerplate) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = DefaultBoilerplatePath + } + + if f.License == "" { + f.License = "apache2" + } + + if f.Licenses == nil { + f.Licenses = make(map[string]string, len(knownLicenses())) + } + + for key, value := range knownLicenses() { + if _, hasLicense := f.Licenses[key]; !hasLicense { + f.Licenses[key] = value + } + } + + if f.Year == "" { + f.Year = fmt.Sprintf("%v", time.Now().Year()) + } + + // Boilerplate given + if f.Boilerplate != "" { + f.TemplateBody = f.Boilerplate + + return nil + } + + f.TemplateBody = boilerplateTemplate + + return nil +} + +const boilerplateTemplate = `/* +{{ if .Owner -}} +Copyright {{ .Year }} {{ .Owner }}. +{{- else -}} +Copyright {{ .Year }}. +{{- end }} +{{ index .Licenses .License }}*/` + +const apache2 = ` +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. +` + +func knownLicenses() map[string]string { + return map[string]string{ + "apache2": apache2, + "copyright": "", + } +} diff --git a/internal/plugins/workload/v2/scaffolds/templates/int/dependencies/component.go b/internal/plugins/workload/v2/scaffolds/templates/int/dependencies/component.go new file mode 100644 index 0000000..8244047 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/int/dependencies/component.go @@ -0,0 +1,53 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package dependencies + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" +) + +var _ machinery.Template = &Component{} + +// Component scaffolds the workload's check ready function that is called by +// components with a dependency on the workload. +type Component struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin +} + +func (f *Component) SetTemplateDefaults() error { + f.Path = filepath.Join( + "internal", + "dependencies", + fmt.Sprintf("%s.go", utils.ToFileName(f.Resource.Kind)), + ) + + f.TemplateBody = componentTemplate + + f.IfExistsAction = machinery.SkipFile + + return nil +} + +const componentTemplate = `{{ .Boilerplate }} + +package dependencies + +import ( + "github.com/nukleros/operator-builder-tools/pkg/controller/workload" +) + +// {{ .Resource.Kind }}CheckReady performs the logic to determine if a {{ .Resource.Kind }} object is ready. +func {{ .Resource.Kind }}CheckReady(r workload.Reconciler, req *workload.Request) (bool, error) { + return true, nil +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/int/mutate/component.go b/internal/plugins/workload/v2/scaffolds/templates/int/mutate/component.go new file mode 100644 index 0000000..8e8e868 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/int/mutate/component.go @@ -0,0 +1,57 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package mutate + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" +) + +var _ machinery.Template = &Component{} + +// Component scaffolds the workload's mutate function. +type Component struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.RepositoryMixin + machinery.ResourceMixin +} + +func (f *Component) SetTemplateDefaults() error { + f.Path = filepath.Join( + "internal", + "mutate", + fmt.Sprintf("%s.go", utils.ToFileName(f.Resource.Kind)), + ) + + f.TemplateBody = componentTemplate + + f.IfExistsAction = machinery.SkipFile + + return nil +} + +const componentTemplate = `{{ .Boilerplate }} + +package mutate + +import ( + "github.com/nukleros/operator-builder-tools/pkg/controller/workload" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// {{ .Resource.Kind }}Mutate performs the logic to mutate resources that belong to the parent. +func {{ .Resource.Kind }}Mutate( + r workload.Reconciler, + req *workload.Request, + object client.Object, +) (replacedObjects []client.Object, skip bool, err error) { + return []client.Object{object}, false, nil +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/main.go b/internal/plugins/workload/v2/scaffolds/templates/main.go new file mode 100644 index 0000000..6bd7fe2 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/main.go @@ -0,0 +1,296 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "fmt" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + + "github.com/nukleros/operator-builder/internal/utils" +) + +const ( + defaultMainPath = utils.DefaultMainPath + importMarker = "imports" + addSchemeMarker = "scheme" + setupMarker = "reconcilers" +) + +var _ machinery.Template = &Main{} + +// Main adds API-specific scaffolding to main.go. +type Main struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.DomainMixin + machinery.RepositoryMixin +} + +func (f *Main) SetTemplateDefaults() error { + f.Path = defaultMainPath + + f.TemplateBody = fmt.Sprintf(mainTemplate, + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, setupMarker), + ) + + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +var _ machinery.Inserter = &MainUpdater{} + +type MainUpdater struct { + machinery.RepositoryMixin + machinery.MultiGroupMixin + machinery.ResourceMixin + + // Flags to indicate which parts need to be included when updating the file + WireResource, WireController, WireWebhook bool +} + +func (*MainUpdater) GetPath() string { + return defaultMainPath +} + +func (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile +} + +func (f *MainUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(defaultMainPath, importMarker), + machinery.NewMarkerFor(defaultMainPath, addSchemeMarker), + machinery.NewMarkerFor(defaultMainPath, setupMarker), + } +} + +const ( + apiImportCodeFragment = `%s "%s" +` + controllerImportCodeFragment = `"%s/controllers" +` + multiGroupControllerImportCodeFragment = `%scontrollers "%s/controllers/%s" +` + addschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme)) +` + reconcilerSetupCodeFragment = `controllers.New%sReconciler(mgr), +` + multiGroupReconcilerSetupCodeFragment = `%scontrollers.New%sReconciler(mgr), +` + webhookSetupCodeFragment = ` +if err = (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "%s") + os.Exit(1) + } +` +) + +func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap { + const options = 3 + + fragments := make(machinery.CodeFragmentsMap, options) + + // If resource is not being provided we are creating the file, not updating it + if f.Resource == nil { + return fragments + } + + // Generate import code fragments + imports := make([]string, 0) + if f.WireResource { + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) + } + + if f.WireController { + if !f.MultiGroup || f.Resource.Group == "" { + imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo)) + } else { + imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment, + f.Resource.PackageName(), f.Repo, f.Resource.Group)) + } + } + + // Generate add scheme code fragments + addScheme := make([]string, 0) + if f.WireResource { + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) + } + + // Generate setup code fragments + setup := make([]string, 0) + + if f.WireController { + if !f.MultiGroup || f.Resource.Group == "" { + setup = append(setup, fmt.Sprintf(reconcilerSetupCodeFragment, f.Resource.Kind)) + } else { + setup = append( + setup, fmt.Sprintf( + multiGroupReconcilerSetupCodeFragment, + f.Resource.PackageName(), + f.Resource.Kind, + ), + ) + } + } + + if f.WireWebhook { + setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, + f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) + } + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports + } + + if len(addScheme) != 0 { + fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme + } + + if len(setup) != 0 { + fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup + } + + return fragments +} + +const mainTemplate = `{{ .Boilerplate }} + +package main + +import ( + "flag" + "os" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/client-go/rest" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/healthz" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + %s +) + +type ReconcilerInitializer interface { + GetName() string + SetupWithManager(ctrl.Manager) error +} + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + %s +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + var secureMetrics bool + var enableHTTP2 bool + + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. " + + "Enabling this will ensure there is only one active controller manager.") + flag.BoolVar(&secureMetrics, "metrics-secure", false, + "If set the metrics endpoint is served securely") + flag.BoolVar(&enableHTTP2, "enable-http2", false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers") + + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + // only print a given warning the first time we receive it + rest.SetDefaultWarningHandler( + rest.NewWarningWriter(os.Stderr, rest.WarningWriterOptions{ + Deduplicate: true, + }), + ) + + // if the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + disableHTTP2 := func(c *tls.Config) { + setupLog.Info("disabling http/2") + c.NextProtos = []string{"http/1.1"} + } + + tlsOpts := []func(*tls.Config){} + if !enableHTTP2 { + tlsOpts = append(tlsOpts, disableHTTP2) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "{{ hashFNV .Repo }}.{{ .Domain }}", + Metrics: metricsserver.Options{ + BindAddress: metricsAddr, + SecureServing: secureMetrics, + TLSOpts: tlsOpts, + }, + }) + + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + reconcilers := []ReconcilerInitializer{ + %s + } + + for _, reconciler := range reconcilers { + if err = reconciler.SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", reconciler.GetName()) + os.Exit(1) + } + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/makefile.go b/internal/plugins/workload/v2/scaffolds/templates/makefile.go new file mode 100644 index 0000000..9dc6968 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/makefile.go @@ -0,0 +1,421 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "fmt" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +var _ machinery.Template = &Makefile{} + +const crdOptions = "crd:crdVersions=v1" + +// Makefile scaffolds the project Makefile. +type Makefile struct { + machinery.TemplateMixin + machinery.RepositoryMixin + machinery.ProjectNameMixin + machinery.DomainMixin + + RootCmdName string + CrdOptions string + ControllerImg string + EnableOLM bool + + ControllerToolsVersion string + KustomizeVersion string + OperatorSDKVersion string + ControllerRuntimeVersion string + EnvtestVersion string + EnvtestK8SVersion string + GolangCILintVersion string +} + +func (f *Makefile) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "Makefile" + } + + f.CrdOptions = crdOptions + f.TemplateBody = fmt.Sprintf(makefileTemplate, f.Domain, f.ProjectName, makeHelp) + + f.IfExistsAction = machinery.OverwriteFile + + //nolint:godox + // TODO: Current workaround for setup-envtest compatibility + // Due to past instances where controller-runtime maintainers released + // versions without corresponding branches, directly relying on branches + // poses a risk of breaking the Kubebuilder chain. Such practices may + // change over time, potentially leading to compatibility issues. This + // approach, although not ideal, remains the best solution for ensuring + // compatibility with controller-runtime releases as of now. For more + // details on the quest for a more robust solution, refer to the issue + // raised in the controller-runtime repository: https://github.com/kubernetes-sigs/controller-runtime/issues/2744 + if f.EnvtestVersion == "" { + f.EnvtestVersion = "latest" + } + + return nil +} + +// NOTE: this is to account for characters which represent string substitutions in go such as %s. +// +//nolint:lll +const makeHelp = `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)` + +//nolint:lll +const makefileTemplate = ` +# Image URL to use all building/pushing image targets +IMG ?= "{{ .ControllerImg }}" + +# Produce CRDs that work back to Kubernetes 1.11 (no version conversion) +CRD_OPTIONS ?= "crd:crdVersions=v1" + +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.30.0 + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# CONTAINER_TOOL defines the container tool to be used for building images. +# Be aware that the target commands are only tested with Docker which is +# scaffolded by default. However, you might want to replace it to use other +# tools. (i.e. podman) +CONTAINER_TOOL ?= docker + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +.PHONY: all +all: build + +##@ 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 + +%[3]s + +##@ Development + +.PHONY: manifests +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./apis/..." paths="./controllers/..." paths="./internal/..." output:crd:artifacts:config=config/crd/bases + +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./apis/..." + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: test +test: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out + +.PHONY: test-e2e +test-e2e: ## Run E2E tests against a running Kubernetes cluster. + go test {{ .Repo }}/test/e2e -tags=e2e_test -count=1 + +.PHONY: lint +lint: golangci-lint ## Run golangci-lint linter + $(GOLANGCI_LINT) run + +.PHONY: lint-fix +lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes + $(GOLANGCI_LINT) run --fix + +##@ Build + +.PHONY: build +build: manifests generate fmt vet ## Build manager binary. + go build -o bin/manager main.go + +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + go run ./main.go + +# If you wish built the manager image targeting other platforms you can use the --platform flag. +# (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it. +# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +.PHONY: docker-build +docker-build: test ## Build docker image with the manager. + $(CONTAINER_TOOL) build -t $(IMG) . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + $(CONTAINER_TOOL) push $(IMG) + +# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/ +# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> then the export will fail) +# To properly provided solutions that supports more than one platform you should use this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: test ## Build and push docker image for the manager for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - $(CONTAINER_TOOL) buildx create --name {{ .ProjectName }}-builder + $(CONTAINER_TOOL) buildx use {{ .ProjectName }}-builder + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - $(CONTAINER_TOOL) buildx rm {{ .ProjectName }}-builder + rm Dockerfile.cross + +.PHONY: build-installer +build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment. + mkdir -p dist + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default > dist/install.yaml + +##@ Deployment + +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests 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 - + +.PHONY: undeploy +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +##@ Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +KUBECTL ?= kubectl +KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION) +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION) +ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION) +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION) + +## Tool Versions +KUSTOMIZE_VERSION ?= {{ .KustomizeVersion }} +CONTROLLER_TOOLS_VERSION ?= {{ .ControllerToolsVersion }} +ENVTEST_VERSION ?= {{ .EnvtestVersion }} +GOLANGCI_LINT_VERSION ?= v1.57.2 + +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. +$(KUSTOMIZE): $(LOCALBIN) + $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. +$(CONTROLLER_GEN): $(LOCALBIN) + $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) + +.PHONY: envtest +envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. +$(ENVTEST): $(LOCALBIN) + $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION)) + +.PHONY: golangci-lint +golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. +$(GOLANGCI_LINT): $(LOCALBIN) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,${GOLANGCI_LINT_VERSION}) + +# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist +# $1 - target path with name of binary (ideally with version) +# $2 - package url which can be installed +# $3 - specific version of package +define go-install-tool +@[ -f $(1) ] || { \ +set -e; \ +package=$(2)@$(3) ;\ +echo "Downloading $${package}" ;\ +GOBIN=$(LOCALBIN) go install $${package} ;\ +mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\ +} +endef + +{{ if .EnableOLM -}} +##@ Operator Lifecycle Manager + +# opm +.PHONY: opm +OPM = ./bin/opm +opm: ## Download opm locally if necessary. +ifeq (,$(wildcard $(OPM))) +ifeq (,$(shell which opm 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPM)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/%[1]s/$${OS}-$${ARCH}-opm ;\ + chmod +x $(OPM) ;\ + } +else +OPM = $(shell which opm) +endif +endif + +# operator-sdk +.PHONY: operator-sdk +OPERATOR_SDK = ./bin/operator-sdk +operator-sdk: ## Download operator-sdk locally if necessary. +ifeq (,$(wildcard $(OPERATOR_SDK))) +ifeq (,$(shell which opm 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPERATOR_SDK)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/{{ .OperatorSDKVersion }}/operator-sdk_$${OS}_$${ARCH} ;\ + chmod +x $(OPERATOR_SDK) ;\ + } +else +OPM = $(shell which opm) +endif +endif + +# bundle + +# VERSION defines the project version for the bundle. +# Update this value when you upgrade the version of your project. +# To re-generate a bundle for another specific version without changing the standard setup, you can: +# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) +# - use environment variables to overwrite this value (e.g export VERSION=0.0.2) +VERSION ?= 0.0.1 + +# CHANNELS define the bundle channels used in the bundle. +# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") +# To re-generate a bundle for other specific channels without changing the standard setup, you can: +# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) +# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") +ifneq ($(origin CHANNELS), undefined) +BUNDLE_CHANNELS := --channels=$(CHANNELS) +endif + +# DEFAULT_CHANNEL defines the default channel used in the bundle. +# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") +# To re-generate a bundle for any other default channel without changing the default setup, you can: +# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) +# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") +ifneq ($(origin DEFAULT_CHANNEL), undefined) +BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) +endif +BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) + +# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. +# This variable is used to construct full image tags for bundle and catalog images. +# +# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both +# %[1]s/%[2]s-bundle:$VERSION and %[1]s/%[2]s-catalog:$VERSION. +IMAGE_TAG_BASE ?= %[1]s/%[2]s + +# BUNDLE_IMG defines the image:tag used for the bundle. +# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) +BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) + +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + +.PHONY: bundle +bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. + operator-sdk generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) + operator-sdk bundle validate ./bundle + +.PHONY: bundle-build +bundle-build: ## Build the bundle image. + docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . + +.PHONY: bundle-push +bundle-push: ## Push the bundle image. + $(MAKE) docker-push IMG=$(BUNDLE_IMG) + + +# catalog + +# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). +# These images MUST exist in a registry and be pull-able. +BUNDLE_IMGS ?= $(BUNDLE_IMG) + +# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). +CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) + +# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. +ifneq ($(origin CATALOG_BASE_IMG), undefined) +FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) +endif + +# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. +# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: +# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator +.PHONY: catalog-build +catalog-build: opm ## Build a catalog image. + $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) + +# Push the catalog image. +.PHONY: catalog-push +catalog-push: ## Push a catalog image. + $(MAKE) docker-push IMG=$(CATALOG_IMG) +{{ end }} + +##@ Miscellaneous + +{{ if ne .RootCmdName "" -}} +.PHONY: build-cli +build-cli: ## Build the companion CLI. + go build -o bin/{{ .RootCmdName }} cmd/{{ .RootCmdName }}/main.go +{{- end }} + +# NOTE: requires go version 1.16 or later +docs: manifests ## Build the API documentation. + @if ! command -v go &> /dev/null; then echo "error: go not installed"; exit 1; fi; \ + GOCMD=$$(which go); \ + if [[ -z $$($$GOCMD version | grep '1.16') ]]; then echo "error: requires go version >= 1.16"; exit 1; fi; \ + go get fybrik.io/crdoc@v0.5.0; \ + go install fybrik.io/crdoc@v0.5.0; \ + crdoc --resources config/crd/bases/ --output docs/apis.md +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/readme.go b/internal/plugins/workload/v2/scaffolds/templates/readme.go new file mode 100644 index 0000000..040fe34 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/readme.go @@ -0,0 +1,140 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "errors" + "fmt" + "strings" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +const ( + missingVersionTag = "latest" +) + +var _ machinery.Template = &Readme{} + +var ErrInvalidImage = errors.New("invalid image") + +// Readme scaffolds a file that defines the templated README.md instructions for a custom workload. +type Readme struct { + machinery.TemplateMixin + + RootCmdName string + EnableOLM bool + ControllerImg string + ControllerBundleImg string +} + +// SetTemplateDefaults implements file.Template. +func (f *Readme) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "README.md" + } + + controllerImgParts := strings.Split(f.ControllerImg, ":") + switch len(controllerImgParts) { + case 1: + f.ControllerImg = fmt.Sprintf("%s:%s", controllerImgParts[0], missingVersionTag) + f.ControllerBundleImg = fmt.Sprintf("%s-bundle:%s", controllerImgParts[0], missingVersionTag) + case 2: + f.ControllerImg = fmt.Sprintf("%s:%s", controllerImgParts[0], controllerImgParts[1]) + f.ControllerBundleImg = fmt.Sprintf("%s-bundle:%s", controllerImgParts[0], controllerImgParts[1]) + default: + return fmt.Errorf("%s; %w", f.ControllerImg, ErrInvalidImage) + } + + f.IfExistsAction = machinery.OverwriteFile + f.TemplateBody = readmefileTemplate + + return nil +} + +const readmefileTemplate = `A Kubernetes operator built with +[operator-builder](https://github.com/nukleros/operator-builder). + +## Local Development & Testing + +To install the custom resource/s for this operator, make sure you have a +kubeconfig set up for a test cluster, then run: + + make install + +To run the controller locally against a test cluster: + + make run + +You can then test the operator by creating the sample manifest/s: + + kubectl apply -f config/samples + +To clean up: + + make uninstall + +## Deploy the Controller Manager + +First, set the image: + + export IMG={{ .ControllerImg }} + +Now you can build and push the image: + + make docker-build + make docker-push + +Then deploy: + + make deploy + +To clean up: + + make undeploy + +{{ if ne .RootCmdName "" -}} +## Companion CLI + +To build the companion CLI: + + make build-cli + +The CLI binary will get saved to the bin directory. You can see the help +message with: + + ./bin/{{ .RootCmdName }} help +{{- end }} + +{{ if .EnableOLM -}} +## Deploy the Operator Lifecycle Manager Bundle + +First, build the bundle. The bundle contains metadata that makes it +compatible with Operator Lifecycle Manager and also makes the operator +importable into OpenShift OperatorHub: + + make bundle + +Next, set the bundle image. This is the image that contains the packaged +bundle: + + export BUNDLE_IMG={{ .ControllerBundleImg }} + +Now you can build and push the bundle image: + + make bundle-build + make bundle-push + +To deploy the bundle (requires OLM to be running in the cluster): + + make operator-sdk + bin/operator-sdk bundle validate $BUNDLE_IMG + bin/operator-sdk run bundle $BUNDLE_IMG + +To clean up: + + bin/operator-sdk cleanup --delete-all $BUNDLE_IMG +{{ end -}} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/test/e2e/e2e.go b/internal/plugins/workload/v2/scaffolds/templates/test/e2e/e2e.go new file mode 100644 index 0000000..e31eab0 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/test/e2e/e2e.go @@ -0,0 +1,876 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package e2e + +import ( + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" +) + +const ( + e2eTestPath = "test/e2e/e2e_test.go" +) + +var _ machinery.Template = &Test{} + +// Test scaffolds the E2E test common code. +type Test struct { + machinery.TemplateMixin + machinery.BoilerplateMixin +} + +func (f *Test) SetTemplateDefaults() error { + f.Path = e2eTestPath + f.IfExistsAction = machinery.OverwriteFile + f.TemplateBody = e2eTestTemplate + + return nil +} + +const e2eTestTemplate = `//go:build e2e_test +// +build e2e_test + +{{ .Boilerplate }} + +package e2e_test + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "gopkg.in/yaml.v2" + + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + k8syaml "sigs.k8s.io/yaml" + + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "github.com/nukleros/operator-builder-tools/pkg/resources" + "github.com/nukleros/operator-builder-tools/pkg/controller/workload" + kbresource "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" +) + +// E2ETestSuiteConfig represents the entire suite of tests. +type E2ETestSuiteConfig struct { + dynamicClient dynamic.Interface + client kubernetes.Clientset + controllerConfig controllerConfig + tests []*E2ETest +} + +type controllerConfig struct { + Namespace string ` + "`" + `yaml:"namespace"` + "`" + ` + Prefix string ` + "`" + `yaml:"namePrefix"` + "`" + ` +} + +// E2EComponentTestSuite represents an indvidual component test. +type E2EComponentTestSuite struct { + suite.Suite + + suiteConfig E2ETestSuiteConfig +} + +// E2ECollectionTestSuite represents an individual collection test. +type E2ECollectionTestSuite struct { + suite.Suite + + suiteConfig E2ETestSuiteConfig +} + +// E2ETest represents an individual test. +type E2ETest struct { + suiteConfig *E2ETestSuiteConfig + namespace string + sampleManifestFile string + unstructured *unstructured.Unstructured + workload workload.Workload + collectionTester *E2ETest + children []client.Object + getChildrenFunc getChildren + logSyntax string +} + +type getChildren func(*E2ETest) error +type readyChecker func() (bool, error) + +const ( + controllerName = "controller-manager" + controllerKustomization = "../../config/default/kustomization.yaml" + waitTimeout = 90 * time.Second + waitInterval = 3 * time.Second +) + +// deletableWhitelist is a representation of known kinds which may be +// deleted for our test +var deletableWhitelist = []string{ + "Deployment", + "Secret", + "ConfigMap", + "DaemonSet", + "Pod", + "Service", + "Ingress", + "StorageClass", +} + +// +// test entrypoint +// +func TestMain(t *testing.T) { + // setup the test suite + e2eTestSuite := new(E2ETestSuiteConfig) + require.NoErrorf(t, setupSuite(e2eTestSuite), "error setting up test suite") + + // setup the tests + collectionSuite := &E2ECollectionTestSuite{suiteConfig: *e2eTestSuite} + componentSuite := &E2EComponentTestSuite{suiteConfig: *e2eTestSuite} + + // execute the tests + t.Run("TestE2ESuite", func(t *testing.T) { + // run collection test suite first + suite.Run(t, collectionSuite) + + // run component test suite, in parallel, next + suite.Run(t, componentSuite) + }) + + // teardown the test suites + componentSuite.teardown() + collectionSuite.teardown() + + // check all controller logs for errors + if os.Getenv("DEPLOY_IN_CLUSTER") == "true" { + require.NoErrorf(t, testControllerLogsNoErrors(e2eTestSuite, ""), "found errors in controller logs") + } + + // perform final teardown + require.NoErrorf(t, finalTeardown(), "error tearing down test suite") +} + +// +// setup +// + +// setupSuite is the common logic for both collection and component tests to run. +func setupSuite(s *E2ETestSuiteConfig) error { + // create rest config from kubeconfig + var err error + var config *rest.Config + if os.Getenv("KUBECONFIG") != "" { + config, err = clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG")) + } else { + config, err = clientcmd.BuildConfigFromFlags("", os.Getenv("HOME")+"/.kube/config") + } + + if err != nil { + return fmt.Errorf("unable to create rest config from kubeconfig; %w", err) + } + + // create client + restClient, err := kubernetes.NewForConfig(config) + if err != nil { + return fmt.Errorf("unable create rest client from kubeconfig; %w", err) + } + s.client = *restClient + + // create dynamic client + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + return fmt.Errorf("unable to create dynamic client from kubeconfig; %w", err) + } + s.dynamicClient = dynamicClient + + // get the controller configuration from yaml + if err := readYamlFile(controllerKustomization, &s.controllerConfig); err != nil { + return fmt.Errorf("unable to fetch controller configuration; %w", err) + } + + // run deploy + return deploy(s) +} + +// SetupTest is called once at the beginning of each test. Component tests run in parallel, +// but collection tests do not. +func (s *E2EComponentTestSuite) SetupTest() { + s.T().Parallel() +} + +// setup is called upon entering a test. This is separate from the above +// method as it populates specific metadata about an individual test that is +// not otherwise available during the SetupTest method. +func (tester *E2ETest) setup() error { + // get the sample manifest from yaml + yamlFile, err := readYamlManifest(tester.sampleManifestFile, tester.unstructured) + if err != nil { + return fmt.Errorf("unable to fetch sample manifest; %w", err) + } + + // get the proper object from the manifest object + if err := k8syaml.Unmarshal(yamlFile, tester.workload); err != nil { + return fmt.Errorf("unable to unmarshal yaml to api object; %w", err) + } + + // ensure the namespace for the underlying manifest matches the tester namespace + tester.unstructured.SetNamespace(tester.namespace) + tester.workload.SetNamespace(tester.namespace) + + // get the proper collection object from the manifest object + if tester.collectionTester != nil { + collection := &unstructured.Unstructured{} + collectionYaml, err := readYamlManifest(tester.collectionTester.sampleManifestFile, collection) + if err != nil { + return fmt.Errorf("unable to fetch sample collection manifest; %w", err) + } + + if err := k8syaml.Unmarshal(collectionYaml, tester.collectionTester.workload); err != nil { + return fmt.Errorf("unable to unmarshal collection yaml to api object; %w", err) + } + + // ensure the namespace for the underlying manifest matches the collection tester namespace + tester.collectionTester.unstructured.SetNamespace(tester.collectionTester.namespace) + tester.collectionTester.workload.SetNamespace(tester.collectionTester.namespace) + } + + // get and store the non-mutated child objects + if err := tester.getChildrenFunc(tester); err != nil { + return fmt.Errorf("unable to unmarshal yaml to api object; %w", err) + } + + // create a namespace for each test case + // NOTE: cluster-scoped resources will not have a namespace and therefore will + // not receive an individual namespace for their test case + if tester.namespace != "" { + if err := createNamespaceForTest(tester); err != nil { + return fmt.Errorf("failed to create namespace for test; %w", err) + } + } + + return nil +} + +// +// deploy +// +// DEPLOY="true" will run all tasks to deploy into the cluster to include: +// - docker build +// - docker push +// - crd install +// - controller deployment +// +// DEPLOY_IN_CLUSTER="true" ensures that the controller is running before proceeding. +// if this option is not used, a separate process such as the 'make run' target +// should be handling the controller functions for the test. +// +func deploy(s *E2ETestSuiteConfig) error { + // install crds + if os.Getenv("DEPLOY") == "true" { + installCommand := exec.Command("make", "-C", "../..", "install") + _, err := installCommand.Output() + if err != nil { + return fmt.Errorf("failed to run 'make install' target; %w", err) + } + } + + if os.Getenv("DEPLOY_IN_CLUSTER") == "true" { + if os.Getenv("DEPLOY") == "true" { + // build image + buildCommand := exec.Command("make", "-C", "../..", "docker-build") + _, err := buildCommand.Output() + if err != nil { + return fmt.Errorf("failed to run 'make docker-build' target; %w", err) + } + + // push image + pushCommand := exec.Command("make", "-C", "../..", "docker-push") + _, err = pushCommand.Output() + if err != nil { + return fmt.Errorf("failed to run 'make docker-push' target; %w", err) + } + + // deploy controller + deployCommand := exec.Command("make", "-C", "../..", "deploy") + _, err = deployCommand.Output() + if err != nil { + return fmt.Errorf("failed to run 'make deploy' target; %w", err) + } + } + + // wait for controller to be ready + if err := waitForController(s); err != nil { + return fmt.Errorf("failed to wait for controller for test; %w", err) + } + } + + return nil +} + +// +// teardown +// +// make undeploy will teardown the operator and all of its associated custom +// resources +// + +// finalTeardown is the last teardown operation that happens in the E2E testing. +func finalTeardown() error { + // run teardown + if os.Getenv("TEARDOWN") == "true" { + var undeployCommand *exec.Cmd + + if os.Getenv("DEPLOY_IN_CLUSTER") == "true" { + undeployCommand = exec.Command("make", "-C", "../..", "undeploy") + } else { + undeployCommand = exec.Command("make", "-C", "../..", "uninstall") + } + + _, err := undeployCommand.Output() + if err != nil { + return fmt.Errorf("failed to run 'make undeploy/uninstall' target with error; %w", err) + } + } + + return nil +} + +// teardownSuite is called once at the very end of all tests. +func teardownSuite(s *E2ETestSuiteConfig) error { + for _, e2eTest := range s.tests { + // delete the custom resources for the tests + if err := deleteCustomResource(e2eTest); err != nil { + return fmt.Errorf("failed to delete custom resource: %+v; %w", e2eTest, err) + } + + // delete the namespaces for the tests + if e2eTest.namespace != "" { + if err := deleteNamespaceForTest(e2eTest); err != nil { + return fmt.Errorf("failed to delete namespace during teardown: %s; %w", e2eTest.namespace, err) + } + } + } + + return nil +} + +// TearDownSuite runs the logic to teardown a collection test suite. +func (s *E2ECollectionTestSuite) teardown() { + if len(s.suiteConfig.tests) > 0 { + require.NoErrorf(s.T(), teardownSuite(&s.suiteConfig), "unable to teardown collection test suite") + } +} + +// TearDownSuite runs the logic to teardown a component test suite. +func (s *E2EComponentTestSuite) teardown() { + if len(s.suiteConfig.tests) > 0 { + require.NoErrorf(s.T(), teardownSuite(&s.suiteConfig), "unable to teardown component test suite") + } +} + +// +// helpers +// +func readYamlManifest(path string, destination *unstructured.Unstructured) ([]byte, error) { + // read the yaml file + yamlFile, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("unable to read file %s; %w", path, err) + } + + // decode yaml into unstructured.Unstructured + dec := serializer.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) + _, _, err = dec.Decode(yamlFile, nil, destination) + if err != nil { + return nil, fmt.Errorf("error decoding sample manifest %s; %w\n\nwith data: %s", path, err, yamlFile) + } + + return yamlFile, nil +} + +func readYamlFile(path string, destination interface{}) error { + // read the yaml file + yamlFile, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("unable to read file %s; %w", path, err) + } + + // store config in memory + if err = yaml.Unmarshal(yamlFile, destination); err != nil { + return fmt.Errorf("unable to unmarshal yaml file %s; %w", path, err) + } + + return nil +} + +func newNamespaceStub(namespaceName string) *v1.Namespace { + return &v1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: resources.NamespaceVersion, + Kind: resources.NamespaceKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: namespaceName, + }, + } +} + +func namespaceExists(tester *E2ETest) (bool, error) { + _, err := tester.suiteConfig.client.CoreV1().Namespaces().Get( + context.TODO(), + tester.namespace, + metav1.GetOptions{}, + ) + if err != nil { + if errors.IsNotFound(err) { + return false, nil + } else { + return false, err + } + } + + return true, nil +} + +func getPlural(kind string) string { + pluralMap := map[string]string{ + "resourcequota": "resourcequotas", + } + plural := kbresource.RegularPlural(kind) + + if pluralMap[plural] != "" { + return pluralMap[plural] + } + + return plural +} + +func getUpdatableChild(tester *E2ETest, name, namespace, kind string) client.Object { + for _, child := range tester.children { + if child.GetObjectKind().GroupVersionKind().Kind == kind { + if child.GetName() == name && child.GetNamespace() == namespace { + return child + } + } + } + + return nil +} + +func getDeletableChild(tester *E2ETest) client.Object { + for _, whitelistKind := range deletableWhitelist { + for _, child := range tester.children { + if child.GetObjectKind().GroupVersionKind().Kind == whitelistKind { + return child + } + } + } + + return nil +} + +func getResourceGVR(resource client.Object) schema.GroupVersionResource { + return schema.GroupVersionResource{ + Group: resource.GetObjectKind().GroupVersionKind().Group, + Version: resource.GetObjectKind().GroupVersionKind().Version, + Resource: getPlural(strings.ToLower(resource.GetObjectKind().GroupVersionKind().Kind)), + } +} + +func getClientForResource(tester *E2ETest, resource client.Object) dynamic.ResourceInterface { + if tester.namespace != "" { + return tester.suiteConfig.dynamicClient.Resource(getResourceGVR(resource)). + Namespace(tester.namespace) + } + + return tester.suiteConfig.dynamicClient.Resource(getResourceGVR(resource)). + Namespace(resource.GetNamespace()) +} + +func getControllerDeployment(s *E2ETestSuiteConfig) (*appsv1.Deployment, error) { + return s.client. + AppsV1().Deployments(s.controllerConfig.Namespace). + Get(context.TODO(), (s.controllerConfig.Prefix + controllerName), metav1.GetOptions{}) +} + +func createCustomResource(tester *E2ETest) error { + _, err := getClientForResource(tester, tester.unstructured). + Create(context.TODO(), tester.unstructured, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("error creating custom resource: %+v; %w", tester.unstructured, err) + } + + return waitForCustomResource(tester) +} + +func createNamespaceForTest(tester *E2ETest) error { + namespaceExists, err := namespaceExists(tester) + if namespaceExists || err != nil { + return err + } + + _, err = tester.suiteConfig.client. + CoreV1().Namespaces(). + Create( + context.TODO(), + newNamespaceStub(tester.namespace), + metav1.CreateOptions{}, + ) + + return err +} + +func getResource(tester *E2ETest, resource client.Object) (client.Object, error) { + clusterObject, err := getClientForResource(tester, resource). + Get(context.TODO(), resource.GetName(), metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("unable to get resource from cluster: %v; %w", clusterObject, err) + } + + return clusterObject, nil +} + +func getControllerLogs(s *E2ETestSuiteConfig) (string, error) { + deployment, err := getControllerDeployment(s) + if err != nil { + return "", fmt.Errorf("unable to retrieve controller deployment; %w", err) + } + + podListOpts := metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet(deployment.Spec.Template.Labels).String(), + } + + controllerPods, err := s.client.CoreV1().Pods(s.controllerConfig.Namespace).List(context.TODO(), podListOpts) + if err != nil { + return "", fmt.Errorf("unable to retrieve controller pods; %w", err) + } + + buf := new(bytes.Buffer) + + for _, pod := range controllerPods.Items { + for _, container := range pod.Spec.Containers { + podLogOpts := v1.PodLogOptions{Container: container.Name} + req := s.client.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts) + + podLogs, err := req.Stream(context.TODO()) + if err != nil { + return "", fmt.Errorf("error opening log stream for pod %s/%s; %w", pod.Namespace, pod.Name, err) + } + + defer podLogs.Close() + + _, err = io.Copy(buf, podLogs) + if err != nil { + return "", fmt.Errorf("error storing logs to string buffer; %w", err) + } + } + } + + return buf.String(), nil +} + +func updateResource(tester *E2ETest, resource client.Object) error { + unstructuredResource, err := resources.ToUnstructured(resource) + if err != nil { + return err + } + + _, err = getClientForResource(tester, resource). + Update(context.TODO(), unstructuredResource, metav1.UpdateOptions{}) + + return err +} + +func deleteResource(tester *E2ETest, resource client.Object) error { + return getClientForResource(tester, resource). + Delete(context.TODO(), resource.GetName(), metav1.DeleteOptions{}) +} + +func deleteCustomResource(tester *E2ETest) error { + crClient := getClientForResource(tester, tester.unstructured) + + _, err := crClient.Get(context.TODO(), tester.unstructured.GetName(), metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + + return err + } + + if err := crClient.Delete(context.TODO(), tester.unstructured.GetName(), metav1.DeleteOptions{}); err != nil { + return fmt.Errorf("error deleting custom resource: %+v; %w", tester.unstructured, err) + } + + return waitForMissingResources(tester) +} + +func deleteNamespaceForTest(tester *E2ETest) error { + err := tester.suiteConfig.client. + CoreV1().Namespaces(). + Delete(context.TODO(), tester.namespace, metav1.DeleteOptions{}) + if err != nil { + return err + } + + namespaceIsMissing := func() (bool, error) { + namespaceExists, err := namespaceExists(tester) + if err != nil { + return false, err + } + + return !namespaceExists, nil + } + + return waitFor(namespaceIsMissing) +} + +func waitForMissingResources(tester *E2ETest) error { + // wait for the resources to be missing + childResourcesAreMissing := func() (bool, error) { + for _, child := range tester.children { + _, err := getClientForResource(tester, child). + Get(context.TODO(), child.GetName(), metav1.GetOptions{}) + + // we expect an IsNotFound error + if err == nil { + return false, nil + } + + if errors.IsNotFound(err) { + continue + } + return false, err + } + + return true, nil + } + + return waitFor(childResourcesAreMissing) +} + +func waitForEqualResources(tester *E2ETest, resource client.Object) error { + // wait for the resources to be equal + childResourceIsEqual := func() (bool, error) { + childResourceClusterObject, err := getClientForResource(tester, resource). + Get(context.TODO(), resource.GetName(), metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("unable to get child resource from cluster: %+v; %w", resource, err) + } + + // return equality statue of resource + return resources.AreEqual(resource, childResourceClusterObject) + } + + return waitFor(childResourceIsEqual) +} + +func waitForChildResources(tester *E2ETest) error { + // wait for the resources to be ready + childResourcesAreReady := func() (bool, error) { + childResourceClusterObjects := make([]client.Object, len(tester.children)) + for i, child := range tester.children { + childResourceClusterObject, err := getClientForResource(tester, child). + Get(context.TODO(), child.GetName(), metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("unable to get child resource from cluster: %+v; %w", child, err) + } + + childResourceClusterObjects[i] = childResourceClusterObject + } + + // get the ready status of the resources + return resources.AreReady(childResourceClusterObjects...) + } + + return waitFor(childResourcesAreReady) +} + +func waitForCustomResource(tester *E2ETest) error { + customResourceIsReady := func() (bool, error) { + customResource, err := getClientForResource(tester, tester.unstructured). + Get(context.TODO(), tester.unstructured.GetName(), metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("unable to get custom resource from cluster: %+v; %w", customResource, err) + } + + // get the created status of the resource + if customResource.Object["status"] == nil { + return false, nil + } + + createStatus := customResource.Object["status"].(map[string]interface{})["created"] + if createStatus != nil { + created, ok := createStatus.(bool) + if !ok { + return false, fmt.Errorf("unable to determine custom resource status") + } + + return created, nil + } + + return false, nil + } + + return waitFor(customResourceIsReady) +} + +func waitForController(s *E2ETestSuiteConfig) error { + deploymentIsReady := func() (bool, error) { + deployment, err := s.client. + AppsV1().Deployments(s.controllerConfig.Namespace). + Get(context.TODO(), (s.controllerConfig.Prefix + controllerName), metav1.GetOptions{}) + if err != nil { + return false, err + } + + return resources.IsReady(deployment) + } + + return waitFor(deploymentIsReady) +} + +func waitFor(isReady readyChecker) error { + timeout, interval := time.After(waitTimeout), time.Tick(waitInterval) + + for { + select { + case <-timeout: + return fmt.Errorf("timed out waiting for resource") + case <-interval: + ready, err := isReady() + if err != nil { + return fmt.Errorf("error waiting for resource to be ready, %w", err) + } + + if ready { + return nil + } + } + } +} + +// +// tests +// +func testCreateCustomResource(tester *E2ETest) error { + _, err := getClientForResource(tester, tester.unstructured). + Create(context.TODO(), tester.unstructured, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("error creating custom resource: %+v; %w", tester.unstructured, err) + } + + // ensure the status ready field gets set + if err = waitForCustomResource(tester); err != nil { + return fmt.Errorf("failed waiting for custom resource ready status: %v; %w", tester.unstructured, err) + } + + // double-check that the child resources are ready + if err = waitForChildResources(tester); err != nil { + return fmt.Errorf("child resources are not in a ready state: %v; %w", tester.unstructured, err) + } + + return nil +} + +func testDeleteChildResource(tester *E2ETest) error { + childToDelete := getDeletableChild(tester) + if childToDelete != nil { + // delete the child resource + if err := deleteResource(tester, childToDelete); err != nil { + return fmt.Errorf("failed deleting child resource;: %+v; %w", childToDelete, err) + } + + // wait for the child resource to return + if err := waitForChildResources(tester); err != nil { + return fmt.Errorf( + "failed waiting for reconciliation after child deletion for resource: %+v; %w", + childToDelete, + err, + ) + } + } + + return nil +} + +func testUpdateParentResource(tester *E2ETest, desiredStateChild client.Object) error { + if desiredStateChild != nil { + // update the parent resource + if err := updateResource(tester, tester.workload); err != nil { + return fmt.Errorf("failed updating parent resource;: %+v; %w", tester.workload, err) + } + + // wait for the child resource to be equal + if err := waitForEqualResources(tester, desiredStateChild); err != nil { + return fmt.Errorf( + "failed waiting for reconciliation after child update for resource: %+v; %w", + desiredStateChild, + err, + ) + } + } + + return nil +} + +func testUpdateChildResource(tester *E2ETest, childToUpdate, desiredStateChild client.Object) error { + if childToUpdate != nil { + // update the child resource + if err := updateResource(tester, childToUpdate); err != nil { + return fmt.Errorf("failed updating child resource: %+v; %w", childToUpdate, err) + } + + // wait for the child resource to be equal + if err := waitForEqualResources(tester, desiredStateChild); err != nil { + return fmt.Errorf( + "failed waiting for reconciliation after child update for resource: %+v; %w", + childToUpdate, + err, + ) + } + } + + return nil +} + +func testControllerLogsNoErrors(s *E2ETestSuiteConfig, searchSyntax string) error { + logs, err := getControllerLogs(s) + if err != nil { + return fmt.Errorf("failed fetching controller logs; %w", err) + } + + errors := []string{} + + for _, logLine := range strings.Split(logs, "\n") { + if strings.Contains(logLine, "ERROR") && strings.Contains(logLine, searchSyntax) { + errors = append(errors, logLine) + } + } + + if len(errors) > 0 { + return fmt.Errorf("found errors in controller: +%v", errors) + } + + return nil +} +` diff --git a/internal/plugins/workload/v2/scaffolds/templates/test/e2e/workloads.go b/internal/plugins/workload/v2/scaffolds/templates/test/e2e/workloads.go new file mode 100644 index 0000000..7976357 --- /dev/null +++ b/internal/plugins/workload/v2/scaffolds/templates/test/e2e/workloads.go @@ -0,0 +1,210 @@ +// Copyright 2024 Nukleros +// Copyright 2021 VMware, Inc. +// SPDX-License-Identifier: MIT + +package e2e + +import ( + "fmt" + "strings" + + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" + + "github.com/nukleros/operator-builder/internal/utils" + "github.com/nukleros/operator-builder/internal/workload/v1/kinds" +) + +const ( + e2eTestWorkloadPath = "test/e2e/%s_%s_%s_test.go" +) + +var ( + _ machinery.Template = &WorkloadTest{} +) + +// WorkloadTest adds API-specific scaffolding for each workload test case. +type WorkloadTest struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.DomainMixin + machinery.RepositoryMixin + machinery.ResourceMixin + + // input fields + Builder kinds.WorkloadBuilder + + // template fields + TesterName string + TesterNamespace string + TesterSamplePath string + TesterCollectionName string + TesterCollectionNamespace string +} + +func (f *WorkloadTest) SetTemplateDefaults() error { + // set template fields + f.TesterNamespace = getTesterNamespace(f.Builder) + f.TesterSamplePath = getTesterSamplePath(f.Resource) + f.TesterName = getTesterName(f.Resource) + + if f.Builder.GetCollection() != nil { + f.TesterCollectionName = getTesterCollectionName(f.Builder.GetCollection()) + f.TesterCollectionNamespace = getTesterNamespace(f.Builder.GetCollection()) + } + + // set interface fields + f.Path = fmt.Sprintf( + e2eTestWorkloadPath, + f.Resource.Group, + f.Resource.Version, + strings.ToLower(f.Resource.Kind), + ) + + f.IfExistsAction = machinery.SkipFile + f.TemplateBody = e2eWorkloadsTemplate + + return nil +} + +//nolint:lll +const e2eWorkloadsTemplate = `// +build e2e_test + +{{ .Boilerplate }} + +package e2e_test + +import ( + "fmt" + "os" + + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" + "{{ .Resource.Path }}/{{ .Builder.GetPackageName }}" +) + +// +// {{ .TesterName }} tests +// +func {{ .TesterName }}ChildrenFuncs(tester *E2ETest) error { + // TODO: need to run r.GetResources(request) on the reconciler to get the mutated resources + if len({{ .Builder.GetPackageName }}.CreateFuncs) == 0 { + return nil + } + + workload, {{ if .Builder.IsComponent }}collection,{{ end }}err := {{ .Builder.GetPackageName }}.ConvertWorkload(tester.workload{{ if .Builder.IsComponent }},tester.collectionTester.workload){{ else }}){{ end }} + if err != nil { + return fmt.Errorf("error in workload conversion; %w", err) + } + + resourceObjects, err := {{ .Builder.GetPackageName }}.Generate(*workload{{ if .Builder.IsComponent }}, *collection, nil, nil){{ else }}, nil, nil){{ end }} + if err != nil { + return fmt.Errorf("unable to create objects in memory; %w", err) + } + + tester.children = resourceObjects + + return nil +} + +func {{ .TesterName }}NewHarness(namespace string) *E2ETest { + return &E2ETest{ + namespace: namespace, + unstructured: &unstructured.Unstructured{}, + workload: &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}, + sampleManifestFile: "{{ .TesterSamplePath }}", + getChildrenFunc: {{ .TesterName }}ChildrenFuncs, + logSyntax: "controllers.{{ .Resource.Group }}.{{ .Resource.Kind }}", + {{ if .Builder.IsComponent -}} + collectionTester: {{ .TesterCollectionName }}NewHarness("{{ .TesterCollectionNamespace }}"), + {{ end }} + } +} + +{{ if .Builder.IsCollection -}} +func (tester *E2ETest) {{ .TesterName }}Test(testSuite *E2ECollectionTestSuite) { +{{ else }} +func (tester *E2ETest) {{ .TesterName }}Test(testSuite *E2EComponentTestSuite) { +{{ end -}} + testSuite.suiteConfig.tests = append(testSuite.suiteConfig.tests, tester) + tester.suiteConfig = &testSuite.suiteConfig + require.NoErrorf(testSuite.T(), tester.setup(), "failed to setup test") + + // create the custom resource + require.NoErrorf(testSuite.T(), testCreateCustomResource(tester), "failed to create custom resource") + + // test the deletion of a child object + require.NoErrorf(testSuite.T(), testDeleteChildResource(tester), "failed to reconcile deletion of a child resource") + + // test the update of a child object + // TODO: need immutable fields so that we can predict which managed fields we can modify to test reconciliation + // see https://github.com/nukleros/operator-builder/issues/24 + + // test the update of a parent object + // TODO: need immutable fields so that we can predict which managed fields we can modify to test reconciliation + // see https://github.com/nukleros/operator-builder/issues/24 + + // test that controller logs do not contain errors + if os.Getenv("DEPLOY_IN_CLUSTER") == "true" { + require.NoErrorf(testSuite.T(), testControllerLogsNoErrors(tester.suiteConfig, tester.logSyntax), "found errors in controller logs") + } +} + +{{ if .Builder.IsCollection -}} +func (testSuite *E2ECollectionTestSuite) Test_{{ .TesterName }}() { +{{ else }} +func (testSuite *E2EComponentTestSuite) Test_{{ .TesterName }}() { +{{ end -}} + tester := {{ .TesterName }}NewHarness("{{ .TesterNamespace }}") + tester.{{ .TesterName }}Test(testSuite) +} + +{{ if and (not .Builder.IsClusterScoped) (not .Builder.IsCollection) }} +func (testSuite *E2EComponentTestSuite) Test_{{ .TesterName }}Multi() { + tester := {{ .TesterName }}NewHarness("{{ .TesterNamespace }}-2") + tester.{{ .TesterName }}Test(testSuite) +} +{{ end }} +` + +func getTesterSamplePath(r *resource.Resource) string { + return strings.Join([]string{ + "../..", + "config", + "samples", + fmt.Sprintf( + "%s_%s_%s.yaml", + r.Group, + r.Version, + utils.ToFileName(r.Kind), + ), + }, "/", + ) +} + +func getTesterNamespace(builder kinds.WorkloadBuilder) (namespace string) { + if !builder.IsClusterScoped() { + namespaceElements := []string{ + "test", + strings.ToLower(builder.GetAPIGroup()), + strings.ToLower(builder.GetAPIVersion()), + strings.ToLower(builder.GetAPIKind()), + } + namespace = strings.Join(namespaceElements, "-") + } + + return namespace +} + +func getTesterName(r *resource.Resource) string { + return r.ImportAlias() + r.Kind +} + +func getTesterCollectionName(collection *kinds.WorkloadCollection) string { + return strings.ToLower(collection.Spec.API.Group) + + strings.ToLower(collection.GetAPIVersion()) + + collection.Spec.API.Kind +} diff --git a/internal/plugins/workload/versions.go b/internal/plugins/workload/versions.go new file mode 100644 index 0000000..b4c1341 --- /dev/null +++ b/internal/plugins/workload/versions.go @@ -0,0 +1,30 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +package workload + +import "os" + +type PluginVersion int + +const ( + PluginVersionUnknown PluginVersion = iota + PluginVersionV1 + PluginVersionV2 +) + +const ( + DefaultPluginVersion = PluginVersionV2 + + EnvPluginVersionVariable = "OPERATOR_BUILDER_PLUGIN_VERSION" + EnvPluginVersionV1 = "v1" + EnvPluginVersionV2 = "v2" +) + +func FromEnv() PluginVersion { + return map[string]PluginVersion{ + "": DefaultPluginVersion, + EnvPluginVersionV1: PluginVersionV1, + EnvPluginVersionV2: PluginVersionV2, + }[os.Getenv(EnvPluginVersionVariable)] +} diff --git a/internal/plugins/workload/versions_test.go b/internal/plugins/workload/versions_test.go new file mode 100644 index 0000000..32f1d6c --- /dev/null +++ b/internal/plugins/workload/versions_test.go @@ -0,0 +1,60 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +package workload + +import ( + "os" + "testing" +) + +func TestFromEnv(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + want PluginVersion + env map[string]string + }{ + { + name: "ensure empty environment returns default version", + want: DefaultPluginVersion, + }, + { + name: "ensure v1 environment returns v1 version", + want: PluginVersionV1, + env: map[string]string{ + EnvPluginVersionVariable: EnvPluginVersionV1, + }, + }, + { + name: "ensure v2 environment returns v2 version", + want: PluginVersionV2, + env: map[string]string{ + EnvPluginVersionVariable: EnvPluginVersionV2, + }, + }, + { + name: "ensure invalid environment returns unknown version", + want: PluginVersionUnknown, + env: map[string]string{ + EnvPluginVersionVariable: "fake", + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + for k, v := range tt.env { + _ = os.Setenv(k, v) + } + + if got := FromEnv(); got != tt.want { + t.Errorf("FromEnv() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/plugins/workload/workload.go b/internal/plugins/workload/workload.go new file mode 100644 index 0000000..4560f31 --- /dev/null +++ b/internal/plugins/workload/workload.go @@ -0,0 +1,12 @@ +// Copyright 2024 Nukleros +// SPDX-License-Identifier: MIT + +package workload + +import "github.com/spf13/pflag" + +// AddFlags adds a consistent set of workload flags across plugin versions and commands. +func AddFlags(fs *pflag.FlagSet, workloadConfigPath *string, enableOlm *bool) { + fs.StringVar(workloadConfigPath, "workload-config", "", "path to workload config file") + fs.BoolVar(enableOlm, "enable-olm", false, "enable support for OpenShift Lifecycle Manager") +} diff --git a/internal/utils/conversion.go b/internal/utils/conversion.go index 002f898..9ec3664 100644 --- a/internal/utils/conversion.go +++ b/internal/utils/conversion.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/utils/files.go b/internal/utils/files.go index b0c3b7d..074d365 100644 --- a/internal/utils/files.go +++ b/internal/utils/files.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT @@ -8,10 +8,15 @@ import ( "errors" "fmt" "io" - "log" "os" "path/filepath" "strings" + + log "github.com/sirupsen/logrus" +) + +const ( + DefaultMainPath = "main.go" ) func ReadStream(fileName string) (io.ReadCloser, error) { diff --git a/internal/utils/functionmap.go b/internal/utils/functionmap.go index 451c5cf..dab7be3 100644 --- a/internal/utils/functionmap.go +++ b/internal/utils/functionmap.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/utils/names.go b/internal/utils/names.go index 32d412f..3ab130b 100644 --- a/internal/utils/names.go +++ b/internal/utils/names.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/utils/utils.go b/internal/utils/utils.go index acbd013..40b6fd6 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/utils/version.go b/internal/utils/version.go index 384df81..07ec5b6 100644 --- a/internal/utils/version.go +++ b/internal/utils/version.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT @@ -12,7 +12,13 @@ const ( GeneratedGoVersionPreferred = "1.22" // makefile and go.mod versions. - ControllerToolsVersion = "v0.15.0" + // NOTE: please ensure the ControllerToolsVersion matches the go.mod file as we + // use this both in code generation as well as the generated project code. + ControllerToolsVersion = "v0.15.0" + + // NOTE: ControllerRuntimeVersion will need to match operator-builder-tools version + // otherwise their could be inconsistencies in method calls which cause + // ambiguous errors. ControllerRuntimeVersion = "v0.17.3" KustomizeVersion = "v5.4.1" GolangCILintVersion = "v1.57.2" diff --git a/internal/workload/v1/commands/companion/cli.go b/internal/workload/v1/commands/companion/cli.go index 0c0cd3b..f38ca8b 100644 --- a/internal/workload/v1/commands/companion/cli.go +++ b/internal/workload/v1/commands/companion/cli.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/commands/companion/cli_internal_test.go b/internal/workload/v1/commands/companion/cli_internal_test.go index d36ff87..69a0032 100644 --- a/internal/workload/v1/commands/companion/cli_internal_test.go +++ b/internal/workload/v1/commands/companion/cli_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/commands/subcommand/create_api.go b/internal/workload/v1/commands/subcommand/create_api.go index 8042764..312740d 100644 --- a/internal/workload/v1/commands/subcommand/create_api.go +++ b/internal/workload/v1/commands/subcommand/create_api.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/commands/subcommand/init.go b/internal/workload/v1/commands/subcommand/init.go index 13c5492..4cc1675 100644 --- a/internal/workload/v1/commands/subcommand/init.go +++ b/internal/workload/v1/commands/subcommand/init.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/commands/subcommand/init_config.go b/internal/workload/v1/commands/subcommand/init_config.go index dcc84ae..c2850e0 100644 --- a/internal/workload/v1/commands/subcommand/init_config.go +++ b/internal/workload/v1/commands/subcommand/init_config.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/config/config.go b/internal/workload/v1/config/config.go index 3345e07..5145a1e 100644 --- a/internal/workload/v1/config/config.go +++ b/internal/workload/v1/config/config.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/config/parse.go b/internal/workload/v1/config/parse.go index 95b6950..ab11d60 100644 --- a/internal/workload/v1/config/parse.go +++ b/internal/workload/v1/config/parse.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/config/parse_internal_test.go b/internal/workload/v1/config/parse_internal_test.go index 87f2056..f4a2925 100644 --- a/internal/workload/v1/config/parse_internal_test.go +++ b/internal/workload/v1/config/parse_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/config/processor.go b/internal/workload/v1/config/processor.go index 04fd707..b34715d 100644 --- a/internal/workload/v1/config/processor.go +++ b/internal/workload/v1/config/processor.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/config/validate.go b/internal/workload/v1/config/validate.go index 2a09f70..41ba77f 100644 --- a/internal/workload/v1/config/validate.go +++ b/internal/workload/v1/config/validate.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/api.go b/internal/workload/v1/kinds/api.go index 5e6c649..4180f4a 100644 --- a/internal/workload/v1/kinds/api.go +++ b/internal/workload/v1/kinds/api.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/api_internal_test.go b/internal/workload/v1/kinds/api_internal_test.go index 9144ff3..e5a1fa4 100644 --- a/internal/workload/v1/kinds/api_internal_test.go +++ b/internal/workload/v1/kinds/api_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/collection.go b/internal/workload/v1/kinds/collection.go index 38b3a58..9785694 100644 --- a/internal/workload/v1/kinds/collection.go +++ b/internal/workload/v1/kinds/collection.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/collection_internal_test.go b/internal/workload/v1/kinds/collection_internal_test.go index fce8be4..da4bfb7 100644 --- a/internal/workload/v1/kinds/collection_internal_test.go +++ b/internal/workload/v1/kinds/collection_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/component.go b/internal/workload/v1/kinds/component.go index 1954f65..662ca3c 100644 --- a/internal/workload/v1/kinds/component.go +++ b/internal/workload/v1/kinds/component.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/component_internal_test.go b/internal/workload/v1/kinds/component_internal_test.go index 7f0bbe5..b71c6bd 100644 --- a/internal/workload/v1/kinds/component_internal_test.go +++ b/internal/workload/v1/kinds/component_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/kinds.go b/internal/workload/v1/kinds/kinds.go index 6a93aa5..5a53fa8 100644 --- a/internal/workload/v1/kinds/kinds.go +++ b/internal/workload/v1/kinds/kinds.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/standalone.go b/internal/workload/v1/kinds/standalone.go index bf01315..81e25d5 100644 --- a/internal/workload/v1/kinds/standalone.go +++ b/internal/workload/v1/kinds/standalone.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/standalone_internal_test.go b/internal/workload/v1/kinds/standalone_internal_test.go index c22ed3d..e56f6a4 100644 --- a/internal/workload/v1/kinds/standalone_internal_test.go +++ b/internal/workload/v1/kinds/standalone_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/kinds/workload.go b/internal/workload/v1/kinds/workload.go index 724ce24..e2a9b7e 100644 --- a/internal/workload/v1/kinds/workload.go +++ b/internal/workload/v1/kinds/workload.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/manifests/child_resource.go b/internal/workload/v1/manifests/child_resource.go index 77fdee1..439701c 100644 --- a/internal/workload/v1/manifests/child_resource.go +++ b/internal/workload/v1/manifests/child_resource.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/manifests/child_resource_internal_test.go b/internal/workload/v1/manifests/child_resource_internal_test.go index cd7ed1a..bdf39cf 100644 --- a/internal/workload/v1/manifests/child_resource_internal_test.go +++ b/internal/workload/v1/manifests/child_resource_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // SPDX-License-Identifier: MIT package manifests diff --git a/internal/workload/v1/manifests/manifest.go b/internal/workload/v1/manifests/manifest.go index d0ff197..08042a8 100644 --- a/internal/workload/v1/manifests/manifest.go +++ b/internal/workload/v1/manifests/manifest.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/manifests/manifest_internal_test.go b/internal/workload/v1/manifests/manifest_internal_test.go index 147159a..dfdd46c 100644 --- a/internal/workload/v1/manifests/manifest_internal_test.go +++ b/internal/workload/v1/manifests/manifest_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // SPDX-License-Identifier: MIT package manifests diff --git a/internal/workload/v1/markers/collection_field_marker.go b/internal/workload/v1/markers/collection_field_marker.go index 2fb639f..960746d 100644 --- a/internal/workload/v1/markers/collection_field_marker.go +++ b/internal/workload/v1/markers/collection_field_marker.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/markers/collection_field_marker_internal_test.go b/internal/workload/v1/markers/collection_field_marker_internal_test.go index 905e820..41f9502 100644 --- a/internal/workload/v1/markers/collection_field_marker_internal_test.go +++ b/internal/workload/v1/markers/collection_field_marker_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/markers/field_marker.go b/internal/workload/v1/markers/field_marker.go index e38fa25..fe46a6c 100644 --- a/internal/workload/v1/markers/field_marker.go +++ b/internal/workload/v1/markers/field_marker.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/markers/field_marker_internal_test.go b/internal/workload/v1/markers/field_marker_internal_test.go index 683369d..39e38de 100644 --- a/internal/workload/v1/markers/field_marker_internal_test.go +++ b/internal/workload/v1/markers/field_marker_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/markers/field_types.go b/internal/workload/v1/markers/field_types.go index f7a896d..4babb10 100644 --- a/internal/workload/v1/markers/field_types.go +++ b/internal/workload/v1/markers/field_types.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/markers/field_types_internal_test.go b/internal/workload/v1/markers/field_types_internal_test.go index d9b1711..248d55d 100644 --- a/internal/workload/v1/markers/field_types_internal_test.go +++ b/internal/workload/v1/markers/field_types_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/markers/markers.go b/internal/workload/v1/markers/markers.go index 2360d4c..4bdcf10 100644 --- a/internal/workload/v1/markers/markers.go +++ b/internal/workload/v1/markers/markers.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/markers/markers_internal_test.go b/internal/workload/v1/markers/markers_internal_test.go index 671f7e6..7c64c4f 100644 --- a/internal/workload/v1/markers/markers_internal_test.go +++ b/internal/workload/v1/markers/markers_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/markers/resource_marker.go b/internal/workload/v1/markers/resource_marker.go index 79b6d78..2e37830 100644 --- a/internal/workload/v1/markers/resource_marker.go +++ b/internal/workload/v1/markers/resource_marker.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/markers/resource_marker_internal_test.go b/internal/workload/v1/markers/resource_marker_internal_test.go index 705ee4f..584f529 100644 --- a/internal/workload/v1/markers/resource_marker_internal_test.go +++ b/internal/workload/v1/markers/resource_marker_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/rbac/rbac.go b/internal/workload/v1/rbac/rbac.go index 56c7a30..75a3781 100644 --- a/internal/workload/v1/rbac/rbac.go +++ b/internal/workload/v1/rbac/rbac.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/rbac/rbac_internal_test.go b/internal/workload/v1/rbac/rbac_internal_test.go index fa073e1..c6dd70d 100644 --- a/internal/workload/v1/rbac/rbac_internal_test.go +++ b/internal/workload/v1/rbac/rbac_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/rbac/role_rule.go b/internal/workload/v1/rbac/role_rule.go index 046dc4b..768c595 100644 --- a/internal/workload/v1/rbac/role_rule.go +++ b/internal/workload/v1/rbac/role_rule.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/rbac/role_rule_internal_test.go b/internal/workload/v1/rbac/role_rule_internal_test.go index 024fbe1..1eed68b 100644 --- a/internal/workload/v1/rbac/role_rule_internal_test.go +++ b/internal/workload/v1/rbac/role_rule_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/rbac/rule.go b/internal/workload/v1/rbac/rule.go index d9125e0..2f3801f 100644 --- a/internal/workload/v1/rbac/rule.go +++ b/internal/workload/v1/rbac/rule.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/rbac/rule_internal_test.go b/internal/workload/v1/rbac/rule_internal_test.go index e1d06c1..3fd9860 100644 --- a/internal/workload/v1/rbac/rule_internal_test.go +++ b/internal/workload/v1/rbac/rule_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/rbac/rules.go b/internal/workload/v1/rbac/rules.go index 0c83e7b..43a3564 100644 --- a/internal/workload/v1/rbac/rules.go +++ b/internal/workload/v1/rbac/rules.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/internal/workload/v1/rbac/rules_internal_test.go b/internal/workload/v1/rbac/rules_internal_test.go index dfee3f1..1017178 100644 --- a/internal/workload/v1/rbac/rules_internal_test.go +++ b/internal/workload/v1/rbac/rules_internal_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/pkg/cli/init.go b/pkg/cli/init.go index fd48b25..04d3c67 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -1,69 +1,135 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT package cli import ( + "bufio" "fmt" + "os" - kbcli "sigs.k8s.io/kubebuilder/v3/pkg/cli" - cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" - cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" - "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" - "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + log "github.com/sirupsen/logrus" + kbcliv3 "sigs.k8s.io/kubebuilder/v3/pkg/cli" + cfgv3old "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + pluginv3 "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" kustomizecommonv1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v1" - kustomizecommonv2alpha "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v2-alpha" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" declarativev1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/declarative/v1" - golangv2 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2" golangv3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3" + kbcliv4 "sigs.k8s.io/kubebuilder/v4/pkg/cli" + cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3" + pluginv4 "sigs.k8s.io/kubebuilder/v4/pkg/plugin" + kustomizecommonv2 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2" + "github.com/nukleros/operator-builder/internal/plugins" configv1 "github.com/nukleros/operator-builder/internal/plugins/config/v1" licensev1 "github.com/nukleros/operator-builder/internal/plugins/license/v1" + licensev2 "github.com/nukleros/operator-builder/internal/plugins/license/v2" + "github.com/nukleros/operator-builder/internal/plugins/workload" workloadv1 "github.com/nukleros/operator-builder/internal/plugins/workload/v1" + workloadv2 "github.com/nukleros/operator-builder/internal/plugins/workload/v2" ) var version = "unstable" -func NewKubebuilderCLI() (*kbcli.CLI, error) { - gov3Bundle, _ := plugin.NewBundle(golang.DefaultNameQualifier, plugin.Version{Number: 3}, - licensev1.Plugin{}, - kustomizecommonv1.Plugin{}, - configv1.Plugin{}, - golangv3.Plugin{}, - workloadv1.Plugin{}, - ) +const ( + commandName = "operator-builder" +) + +type command interface { + Run() error +} + +func NewKubebuilderCLI(version workload.PluginVersion) (command, error) { + switch version { + case workload.PluginVersionV1: + return NewWithV1() + case workload.PluginVersionV2: + return NewWithV2() + default: + return NewKubebuilderCLI(workload.DefaultPluginVersion) + } +} - gov4Bundle, _ := plugin.NewBundle(golang.DefaultNameQualifier, plugin.Version{Number: 4, Stage: stage.Alpha}, +func NewWithV1() (*kbcliv3.CLI, error) { + // the v1 version of the plugin will be deprecated in a future release. notify the user and ask if they want + // to proceed. + log.Println("workload v1 plugin selected, but will be deprecated in a future release. Proceed with v1 [y/n]?") + + reader := bufio.NewReader(os.Stdin) + proceed := util.YesNo(reader) + if !proceed { + return nil, nil + } + + // we cannot upgrade to the latest v4 bundle in kubebuilder as it breaks several dependencies and + // disallows flexibilities that we currently use such as the apis/ directory versus the api/ directory. + base, err := pluginv3.NewBundle(golang.DefaultNameQualifier, pluginv3.Version{Number: 3}, licensev1.Plugin{}, - kustomizecommonv2alpha.Plugin{}, + kustomizecommonv1.Plugin{}, configv1.Plugin{}, golangv3.Plugin{}, workloadv1.Plugin{}, ) + if err != nil { + return nil, fmt.Errorf("unable to initialize kubebuilder plugin bundle with version 1 plugin, %w", err) + } - c, err := kbcli.New( - kbcli.WithCommandName("operator-builder"), - kbcli.WithVersion(version), - kbcli.WithPlugins( - golangv2.Plugin{}, - gov3Bundle, - gov4Bundle, + c, err := kbcliv3.New( + kbcliv3.WithCommandName(commandName), + kbcliv3.WithVersion(version), + kbcliv3.WithPlugins( + base, &licensev1.Plugin{}, &kustomizecommonv1.Plugin{}, &declarativev1.Plugin{}, &workloadv1.Plugin{}, ), - kbcli.WithDefaultPlugins(cfgv2.Version, golangv2.Plugin{}), - kbcli.WithDefaultPlugins(cfgv3.Version, gov3Bundle), - kbcli.WithDefaultProjectVersion(cfgv3.Version), - kbcli.WithExtraCommands(NewUpdateCmd()), - kbcli.WithExtraCommands(NewInitConfigCmd()), - kbcli.WithCompletion(), + kbcliv3.WithDefaultPlugins(cfgv3old.Version, base), + kbcliv3.WithDefaultProjectVersion(cfgv3old.Version), + kbcliv3.WithExtraCommands(NewUpdateCmd()), + kbcliv3.WithExtraCommands(NewInitConfigCmd()), + kbcliv3.WithCompletion(), + ) + if err != nil { + return nil, fmt.Errorf("unable to create command with version 1 plugin, %w", err) + } + + return c, nil +} + +func NewWithV2() (*kbcliv4.CLI, error) { + base, err := pluginv4.NewBundleWithOptions( + pluginv4.WithName(plugins.DefaultNameQualifier), + pluginv4.WithVersion(pluginv4.Version{Number: 2}), + pluginv4.WithPlugins( + licensev2.Plugin{}, + kustomizecommonv2.Plugin{}, + workloadv2.Plugin{}, + ), + ) + if err != nil { + return nil, fmt.Errorf("unable to initialize kubebuilder plugin bundle with version 2 plugin, %w", err) + } + + c, err := kbcliv4.New( + kbcliv4.WithCommandName(commandName), + kbcliv4.WithVersion(version), + kbcliv4.WithPlugins( + base, + &licensev2.Plugin{}, + kustomizecommonv2.Plugin{}, + workloadv2.Plugin{}, + ), + kbcliv4.WithDefaultPlugins(cfgv3.Version, base), + kbcliv4.WithDefaultProjectVersion(cfgv3.Version), + kbcliv4.WithExtraCommands(NewInitConfigCmd()), + kbcliv4.WithCompletion(), ) if err != nil { - return nil, fmt.Errorf("unable to create kcli command, %w", err) + return nil, fmt.Errorf("unable to create command with version 2 plugin, %w", err) } return c, nil diff --git a/pkg/cli/init_config.go b/pkg/cli/init_config.go index 52530a7..459ab63 100644 --- a/pkg/cli/init_config.go +++ b/pkg/cli/init_config.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/pkg/cli/license.go b/pkg/cli/license.go index 8c16199..f9ee4f5 100644 --- a/pkg/cli/license.go +++ b/pkg/cli/license.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT diff --git a/pkg/cli/update.go b/pkg/cli/update.go index 51c7ce9..4537b7a 100644 --- a/pkg/cli/update.go +++ b/pkg/cli/update.go @@ -1,4 +1,4 @@ -// Copyright 2023 Nukleros +// Copyright 2024 Nukleros // Copyright 2021 VMware, Inc. // SPDX-License-Identifier: MIT