diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..104b977 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + labels: + - "skip-release-notes" + - "dependencies" + open-pull-requests-limit: 20 # setting a higher number so we can bundle all the weekly updates together + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: weekly + labels: + - "skip-release-notes" + - "dependencies" + open-pull-requests-limit: 10 # setting a higher number so we can bundle all the weekly updates together diff --git a/.github/workflows/check-manifest-generation-diff.yaml b/.github/workflows/check-manifest-generation-diff.yaml new file mode 100644 index 0000000..4bddac3 --- /dev/null +++ b/.github/workflows/check-manifest-generation-diff.yaml @@ -0,0 +1,34 @@ +name: Check for diff after manifest and generated targets + +on: + pull_request: {} + +jobs: + diff-check-manifests: + name: Check for diff + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Make manifests && generate + run: | + make manifests && make generate + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: '${{ github.workspace }}/go.mod' + - name: Restore Go cache + uses: actions/cache@v4 + with: + path: /home/runner/work/_temp/_github_home/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: go mod tidy + run: | + go mod tidy + - name: Check for diff + run: | + git diff --exit-code --shortstat diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 98ae90b..0eabe16 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,32 +3,54 @@ on: push: tags: - 'v*' + +permissions: + contents: read +env: + REGISTRY: ghcr.io + DOCKERFILE: ${{ github.workspace }}/goreleaser.dockerfile + jobs: - build-push: + release: + permissions: + contents: 'write' + id-token: 'write' + pull-requests: 'read' + repository-projects: 'write' + packages: 'write' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Prepare id: prep run: | - echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version-file: '${{ github.workspace }}/go.mod' - - name: Cache go-build and mod - uses: actions/cache@v3 + VERSION=sha-${GITHUB_SHA::8} + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF/refs\/tags\//} + fi + echo ::set-output name=BUILD_DATE::$(date -u +'%Y-%m-%dT%H:%M:%SZ') + echo ::set-output name=VERSION::${VERSION} + - name: Generate manifests + run: | + mkdir -p output + kustomize build ./config/default > ./output/install.yaml + - name: Log in to the Container registry + uses: docker/login-action@v3 with: - path: | - ~/.cache/go-build/ - ~/go/pkg/mod/ - key: go-${{ hashFiles('go.sum') }} - restore-keys: | - go- + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install Helm + uses: azure/setup-helm@v3 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v3.2.0 + uses: goreleaser/goreleaser-action@v5 with: version: latest - args: release --release-notes=docs/release_notes/${{ env.RELEASE_VERSION }}.md --skip-validate + args: release --release-notes=docs/release_notes/${{ steps.prep.outputs.VERSION }}.md --skip-validate env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build and release the helm charts + run: | + helm registry login ghcr.io -u skarlso -p ${{ secrets.GITHUB_TOKEN }} + helm package --version ${{ steps.prep.outputs.VERSION }} --app-version ${{ steps.prep.outputs.VERSION }} ./crd-bootstrap + helm push ${{ github.event.repository.name }}-${{ steps.prep.outputs.VERSION }}.tgz oci://ghcr.io/skarlso/helm diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..b95cb3d --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,38 @@ +name: test and lint + +on: + pull_request: + paths-ignore: + - 'CODE_OF_CONDUCT.md' + - 'README.md' + - 'Contributing.md' + workflow_call: + + push: + branches: + - main + +permissions: + contents: read # for actions/checkout to fetch code + +jobs: + run-test-suite: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: '${{ github.workspace }}/go.mod' + - name: Restore Go cache + uses: actions/cache@v4 + with: + path: /home/runner/work/_temp/_github_home/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Run lint + run: make lint + - name: Run tests + run: make test diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..d087b65 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,154 @@ +run: + go: "1.22" + timeout: 10m + tests: false + allow-parallel-runners: true + issues-exit-code: 2 + skip-dirs: + - "hack" + +linters: + enable-all: true + disable: + # We are working on it + - wrapcheck + - depguard + # Logical next step + - forcetypeassert # Priority: that can lead to serious crashes. + - exportloopref + - goerr113 # Do not define dynamic errors with Errorf. + - varnamelen # m, d, p < These are not so meaningful variables. + - testpackage # Blackbox testing is preffered. + - funlen # Break long functions. + - gomnd # Give constant values a name with constants. + - ireturn # Accept interface, return concrate. + - nestif # Some nexted if statements are 8 or 9 deep. + - dupl # Check code duplications. + - cyclop # Complex functions are not good. + - gochecknoinits # Init functions cause an import to have side effects, + # and side effects are hard to test, + # reduce readability and increase the complexity of code. + - containedctx # Struct should not contain context, action does. + - nilnil # A function should return either something valuable + # or an error, but both value and error as nil is + # useless. Like when I call it, why is it nil? Tell me + # in an error why. + - bodyclose + - unparam + - nonamedreturns # Either named return, or use simply `return`. + + # Opinionated (we may want to keep it disabled) + - gochecknoglobals + - lll + - paralleltest + - tagliatelle + - wsl + - interfacebloat + + + # Disabled with reason + - dogsled + - exhaustruct # Doesn't really make sense. + - exhaustive # Doesn't really make sense. + - logrlint # Doesn't really make sense. + - goimports # acts weirdly, dci handles imports anyway + + # Disabled because of generics in go 1.18 + - contextcheck + - rowserrcheck + - sqlclosecheck + - wastedassign + + # Deprecated + - deadcode + - exhaustivestruct + - golint + - ifshort + - interfacer + - maligned + - scopelint + - structcheck + - varcheck + - gci + - nosnakecase + +linters-settings: + gci: + sections: + - standard + - blank + - dot + - default + - prefix(github.com/open-component-model/ocm) + custom-order: true + staticcheck: + go: "1.22" + stylecheck: + go: "1.22" + funlen: + lines: 110 + statements: 60 + cyclop: + max-complexity: 45 + skip-tests: true + gocognit: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 45 + nolintlint: + allow-unused: false + require-explanation: true + require-specific: false + varnamelen: + ignore-names: + - err + - wg + - id + govet: + check-shadowing: true + lll: + line-length: 120 + gosec: + exclude-generated: true + +issues: + exclude: + - composites + exclude-rules: + - path: cmds/ + linters: + - forbidigo + - text: "should not use dot imports|don't use an underscore in package name" + linters: + - golint + - source: "https://" + linters: + - lll + - text: "shadow: declaration of \"err\"" + linters: + - govet + - text: "shadow: declaration of \"ok\"" + linters: + - govet + - path: _test\.go + linters: + - goerr113 + - gocyclo + - errcheck + - gosec + - dupl + - funlen + - scopelint + - text: "Spec.DeepCopyInto undefined" + linters: + - typecheck + - text: "G601: Implicit memory aliasing in for loop" + # Ignored cos why not, that was the request. + linters: + - gosec + - source: "// .* #\\d+" + linters: + - godox + - path: ignore/.*\.go + linters: + - dupword diff --git a/cmd/generate.go b/cmd/generate.go index cda524f..b93bad2 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -16,12 +16,13 @@ import ( ) var ( - // generateCmd is root for various `generate ...` commands + // generateCmd is root for various `generate ...` commands. generateCmd = &cobra.Command{ Use: "generate", Short: "Simply generate a CRD output.", RunE: runGenerate, } + fileLocation string url string output string @@ -40,7 +41,7 @@ func init() { f.BoolVarP(&comments, "comments", "m", false, "If set, it will add descriptions as comments to each line where available") } -func runGenerate(cmd *cobra.Command, args []string) error { +func runGenerate(_ *cobra.Command, _ []string) error { var ( content []byte err error diff --git a/cmd/root.go b/cmd/root.go index d45e5c7..1412370 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,16 +2,14 @@ package cmd import "github.com/spf13/cobra" -var ( - rootCmd = &cobra.Command{ - Use: "crd-to-sample", - Short: "CRDs to a Sample file.", - Run: ShowUsage, - } -) +var rootCmd = &cobra.Command{ + Use: "crd-to-sample", + Short: "CRDs to a Sample file.", + Run: ShowUsage, +} // ShowUsage shows usage of the given command on stdout. -func ShowUsage(cmd *cobra.Command, args []string) { +func ShowUsage(cmd *cobra.Command, _ []string) { _ = cmd.Usage() } diff --git a/go.mod b/go.mod index 36e7314..caf5d5f 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,14 @@ go 1.21 require ( github.com/maxence-charriere/go-app/v9 v9.8.0 github.com/spf13/cobra v1.8.0 + github.com/stretchr/testify v1.8.4 + gopkg.in/yaml.v2 v2.4.0 k8s.io/apiextensions-apiserver v0.29.1 k8s.io/apimachinery v0.29.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -18,11 +21,12 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index afeb187..68289f9 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,17 @@ -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -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.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -35,16 +30,14 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY 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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -56,8 +49,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -68,8 +59,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -89,29 +78,17 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= -k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= -k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= -k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 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= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 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/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 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.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 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/pkg/fetcher/fetch_content.go b/pkg/fetcher/fetch_content.go index 56bc85c..d5390f1 100644 --- a/pkg/fetcher/fetch_content.go +++ b/pkg/fetcher/fetch_content.go @@ -1,6 +1,7 @@ package fetcher import ( + "context" "fmt" "io" "net/http" @@ -20,17 +21,19 @@ func NewFetcher(client *http.Client) *Fetcher { // Fetch constructs a request and does a client.Do with it. func (f *Fetcher) Fetch(url string) ([]byte, error) { - req, err := http.NewRequest(http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) if err != nil { return nil, fmt.Errorf("failed to generate request for url '%s': %w", url, err) } + resp, err := f.client.Do(req) if err != nil { return nil, fmt.Errorf("failed to fetch data: %w", err) } + defer func() { if closeErr := resp.Body.Close(); closeErr != nil { - err = fmt.Errorf("failed to close with %s after %w", closeErr, err) + err = fmt.Errorf("failed to close with %w after %w", closeErr, err) } }() @@ -42,5 +45,6 @@ func (f *Fetcher) Fetch(url string) ([]byte, error) { if err != nil { return nil, fmt.Errorf("failed to read body: %w", err) } + return content, nil } diff --git a/pkg/generate.go b/pkg/generate.go index 29a15f4..c3b1a7b 100644 --- a/pkg/generate.go +++ b/pkg/generate.go @@ -10,7 +10,7 @@ import ( "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" ) -// Generate takes a CRD content and path, and outputs +// Generate takes a CRD content and path, and outputs. func Generate(crd *v1beta1.CustomResourceDefinition, w io.WriteCloser, enableComments bool) (err error) { defer func() { if cerr := w.Close(); cerr != nil { @@ -48,11 +48,12 @@ func (w *writer) write(wc io.Writer, msg string) { // It will recursively parse every "properties:" and "additionalProperties:". Using the types, it will also output // some sample data based on those types. func ParseProperties(group, version, kind string, properties map[string]v1beta1.JSONSchemaProps, file io.Writer, indent int, inArray, comments bool) error { - var sortedKeys []string + sortedKeys := make([]string, 0, len(properties)) for k := range properties { sortedKeys = append(sortedKeys, k) } sort.Strings(sortedKeys) + w := &writer{} for _, k := range sortedKeys { if inArray { @@ -68,15 +69,19 @@ func ParseProperties(group, version, kind string, properties map[string]v1beta1. w.write(file, comment.String()) } + w.write(file, fmt.Sprintf("%s%s:", strings.Repeat(" ", indent), k)) } - if len(properties[k].Properties) == 0 && properties[k].AdditionalProperties == nil { + switch { + case len(properties[k].Properties) == 0 && properties[k].AdditionalProperties == nil: if k == "apiVersion" { w.write(file, fmt.Sprintf(" %s/%s\n", group, version)) + continue } if k == "kind" { w.write(file, fmt.Sprintf(" %s\n", kind)) + continue } // If we are dealing with an array, and we have properties to parse @@ -91,13 +96,13 @@ func ParseProperties(group, version, kind string, properties map[string]v1beta1. result = outputValueType(properties[k]) w.write(file, fmt.Sprintf(" %s\n", result)) } - } else if len(properties[k].Properties) > 0 { + case len(properties[k].Properties) > 0: w.write(file, "\n") // recursively parse all sub-properties if err := ParseProperties(group, version, kind, properties[k].Properties, file, indent+2, false, comments); err != nil { return err } - } else if properties[k].AdditionalProperties != nil { + case properties[k].AdditionalProperties != nil: if len(properties[k].AdditionalProperties.Schema.Properties) == 0 { w.write(file, " {}\n") } else { @@ -108,9 +113,11 @@ func ParseProperties(group, version, kind string, properties map[string]v1beta1. } } } + if w.err != nil { return fmt.Errorf("failed to write to file: %w", w.err) } + return nil } @@ -120,9 +127,10 @@ func outputValueType(v v1beta1.JSONSchemaProps) string { return string(v.Default.Raw) } + st := "string" switch v.Type { - case "string": - return "string" + case st: + return st case "integer": return "1" case "boolean": @@ -132,12 +140,14 @@ func outputValueType(v v1beta1.JSONSchemaProps) string { case "array": // deal with arrays of other types that weren't objects t := v.Items.Schema.Type var s string - if t == "string" { + if t == st { s = fmt.Sprintf("[\"%s\"]", t) } else { s = fmt.Sprintf("[%s]", t) } + return s } + return v.Type } diff --git a/pkg/generate_test.go b/pkg/generate_test.go index e50dd56..bc22aad 100644 --- a/pkg/generate_test.go +++ b/pkg/generate_test.go @@ -1,7 +1,32 @@ package pkg -import "testing" +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apimachinery/pkg/util/yaml" +) func TestGenerate(t *testing.T) { + content, err := os.ReadFile(filepath.Join("testdata", "sample_crd.yaml")) + require.NoError(t, err) + + crd := &v1beta1.CustomResourceDefinition{} + require.NoError(t, yaml.Unmarshal(content, crd)) + + var output []byte + buffer := bytes.NewBuffer(output) + + version := crd.Spec.Versions[0] + require.NoError(t, ParseProperties(crd.Spec.Group, version.Name, crd.Spec.Names.Kind, version.Schema.OpenAPIV3Schema.Properties, buffer, 0, false, false)) + + golden, err := os.ReadFile(filepath.Join("testdata", "sample_crd_golden.yaml")) + require.NoError(t, err) + assert.Equal(t, golden, buffer.Bytes()) } diff --git a/pkg/testdata/sample_crd.yaml b/pkg/testdata/sample_crd.yaml new file mode 100644 index 0000000..f04ad89 --- /dev/null +++ b/pkg/testdata/sample_crd.yaml @@ -0,0 +1,87 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: krokcommands.delivery.krok.app +spec: + group: delivery.krok.app + names: + kind: KrokCommand + listKind: KrokCommandList + plural: krokcommands + singular: krokcommand + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: KrokCommand is the Schema for the krokcommands API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: KrokCommandSpec defines the desired state of KrokCommand + properties: + commandHasOutputToWrite: + description: CommandHasOutputToWrite if defined, it signals the underlying + Job, to put its output into a generated and created secret. + type: boolean + dependencies: + description: Dependencies defines a list of command names that this + command depends on. + items: + type: string + type: array + enabled: + description: Enabled defines if this command can be executed or not. + type: boolean + image: + description: 'Image defines the image name and tag of the command + example: krok-hook/slack-notification:v0.0.1' + type: string + platforms: + description: Platforms holds all the platforms which this command + supports. + items: + type: string + type: array + readInputFromSecret: + description: ReadInputFromSecret if defined, the command will take + a list of key/value pairs in a secret and apply them as arguments + to the command. + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + schedule: + description: 'Schedule of the command. example: 0 * * * * // follows + cron job syntax.' + type: string + required: + - image + type: object + status: + description: KrokCommandStatus defines the observed state of KrokCommand + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/pkg/testdata/sample_crd_golden.yaml b/pkg/testdata/sample_crd_golden.yaml new file mode 100644 index 0000000..b127f84 --- /dev/null +++ b/pkg/testdata/sample_crd_golden.yaml @@ -0,0 +1,14 @@ +apiVersion: delivery.krok.app/v1alpha1 +kind: KrokCommand +metadata: {} +spec: + commandHasOutputToWrite: true + dependencies: ["string"] + enabled: true + image: string + platforms: ["string"] + readInputFromSecret: + name: string + namespace: string + schedule: string +status: {} diff --git a/wasm/app.go b/wasm/app.go index 2a58437..80f1a17 100644 --- a/wasm/app.go +++ b/wasm/app.go @@ -95,7 +95,8 @@ func (h *crdView) Render() app.UI { app.Button().Class("accordion-button").Type("button").DataSets( map[string]any{ "bs-toggle": "collapse", - "bs-target": "#yaml-accordion-collapse-" + version.Version}). + "bs-target": "#yaml-accordion-collapse-" + version.Version, + }). Aria("expanded", "false"). Aria("controls", "yaml-accordion-collapse-"+version.Version). Body(app.Text("Details")), @@ -134,6 +135,7 @@ func (h *crdView) Render() app.UI { render(app.Div().Class("accordion-item"), version.Properties, "version-accordion-"+version.Version, 0), ), ) + return div })) @@ -158,7 +160,7 @@ func render(d app.UI, p []*Property, accordionID string, depth int) app.UI { borderOpacity = "" } - var elements []app.UI + elements := make([]app.UI, 0, len(p)) for _, prop := range p { // add the parent first headerElements := []app.UI{ @@ -189,12 +191,14 @@ func render(d app.UI, p []*Property, accordionID string, depth int) app.UI { ), ) - button := app.Button().ID("accordion-button-id-"+prop.Name+accordionID).Class("accordion-button").Type("button").DataSets( + buttonID := "accordion-button-id-" + prop.Name + accordionID + button := app.Button().ID(buttonID).Class("accordion-button").Type("button").DataSets( map[string]any{ "bs-toggle": "collapse", - "bs-target": "#accordion-collapse-for-" + prop.Name + accordionID}). + "bs-target": buttonID, + }). Aria("expanded", "false"). - Aria("controls", "accordion-collapse-for-"+prop.Name+accordionID). + Aria("controls", buttonID). Body( headerContainer, ) @@ -212,14 +216,15 @@ func render(d app.UI, p []*Property, accordionID string, depth int) app.UI { continue } - accordionDiv := app.Div().Class("accordion-collapse collapse").ID("accordion-collapse-for-"+prop.Name+accordionID).DataSet("bs-parent", "#"+accordionID) + accordionDivID := "accordion-collapse-for-" + prop.Name + accordionID + accordionDiv := app.Div().Class("accordion-collapse collapse").ID(accordionDivID).DataSet("bs-parent", "#"+accordionID) accordionBody := app.Div().Class("accordion-body") var bodyElements []app.UI // add any children that the parent has if len(prop.Properties) > 0 { - element := render(app.Div().ID(prop.Name).Class("accordion-item"), prop.Properties, "accordion-collapse-for-"+prop.Name+accordionID, depth+1) + element := render(app.Div().ID(prop.Name).Class("accordion-item"), prop.Properties, accordionDivID, depth+1) bodyElements = append(bodyElements, element) } @@ -229,6 +234,7 @@ func render(d app.UI, p []*Property, accordionID string, depth int) app.UI { } // add all the elements and return the div + //nolint: gocritic // type switch switch t := d.(type) { case app.HTMLDiv: t.Body(elements...) @@ -241,14 +247,15 @@ func render(d app.UI, p []*Property, accordionID string, depth int) app.UI { // parseCRD takes the properties and constructs a linked list out of the embedded properties that the recursive // template can call and construct linked divs. func parseCRD(properties map[string]v1beta1.JSONSchemaProps, version string, requiredList []string) ([]*Property, error) { - var ( - sortedKeys []string - output []*Property - ) + sortedKeys := make([]string, 0, len(properties)) + output := make([]*Property, 0, len(properties)) + for k := range properties { sortedKeys = append(sortedKeys, k) } + sort.Strings(sortedKeys) + for _, k := range sortedKeys { // Create the Property with the values necessary. // Check if there are properties for it in Properties or in Array -> Properties. @@ -259,6 +266,7 @@ func parseCRD(properties map[string]v1beta1.JSONSchemaProps, version string, req for _, item := range requiredList { if item == k { required = true + break } } @@ -276,21 +284,22 @@ func parseCRD(properties map[string]v1beta1.JSONSchemaProps, version string, req p.Default = string(v.Default.Raw) } - if len(properties[k].Properties) > 0 && properties[k].AdditionalProperties == nil { + switch { + case len(properties[k].Properties) > 0 && properties[k].AdditionalProperties == nil: requiredList = v.Required out, err := parseCRD(properties[k].Properties, version, requiredList) if err != nil { return nil, err } p.Properties = out - } else if properties[k].Type == "array" && properties[k].Items.Schema != nil && len(properties[k].Items.Schema.Properties) > 0 { + case properties[k].Type == "array" && properties[k].Items.Schema != nil && len(properties[k].Items.Schema.Properties) > 0: requiredList = v.Required out, err := parseCRD(properties[k].Items.Schema.Properties, version, requiredList) if err != nil { return nil, err } p.Properties = out - } else if properties[k].AdditionalProperties != nil { + case properties[k].AdditionalProperties != nil: requiredList = v.Required out, err := parseCRD(properties[k].AdditionalProperties.Schema.Properties, version, requiredList) if err != nil { @@ -298,7 +307,9 @@ func parseCRD(properties map[string]v1beta1.JSONSchemaProps, version string, req } p.Properties = out } + output = append(output, p) } + return output, nil } diff --git a/wasm/index.go b/wasm/index.go index f63a533..554b81f 100644 --- a/wasm/index.go +++ b/wasm/index.go @@ -29,7 +29,7 @@ func (i *index) buildError() app.UI { ) } -func (i *index) dismissError(ctx app.Context, e app.Event) { +func (i *index) dismissError(_ app.Context, _ app.Event) { i.err = nil } @@ -100,7 +100,7 @@ func (f *form) Render() app.UI { ) } -func (i *index) OnClick(ctx app.Context, e app.Event) { +func (i *index) OnClick(_ app.Context, _ app.Event) { ta := app.Window().GetElementByID("crd_data").Get("value") if v := ta.String(); v != "" { if len(v) > maximumBytes { @@ -149,11 +149,11 @@ func (c *checkBox) Render() app.UI { ) } -func (i *index) OnCheck(ctx app.Context, e app.Event) { +func (i *index) OnCheck(_ app.Context, _ app.Event) { i.comments = !i.comments } -func (i *index) OnMount(ctx app.Context) { +func (i *index) OnMount(_ app.Context) { i.isMounted = true } diff --git a/wasm/main.go b/wasm/main.go index ca1d556..4d962c7 100644 --- a/wasm/main.go +++ b/wasm/main.go @@ -99,6 +99,7 @@ func main() { os.Exit(0) } + //nolint: gosec // it's fine if err := http.ListenAndServe(":8000", nil); err != nil { log.Fatal(err) }