Bump sigs.k8s.io/controller-tools from 0.6.2 to 0.11.1 (#5432)

This commit is contained in:
hongming
2022-12-28 10:12:00 +08:00
committed by GitHub
parent 1e2a8c1699
commit 06978f123d
52 changed files with 1335 additions and 964 deletions

View File

@@ -1,79 +1,79 @@
# Finite State Entropy
This package provides Finite State Entropy encoding and decoding.
Finite State Entropy (also referenced as [tANS](https://en.wikipedia.org/wiki/Asymmetric_numeral_systems#tANS))
encoding provides a fast near-optimal symbol encoding/decoding
for byte blocks as implemented in [zstandard](https://github.com/facebook/zstd).
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/fse)
## News
* Feb 2018: First implementation released. Consider this beta software for now.
# Usage
This package provides a low level interface that allows to compress single independent blocks.
Each block is separate, and there is no built in integrity checks.
This means that the caller should keep track of block sizes and also do checksums if needed.
Compressing a block is done via the [`Compress`](https://godoc.org/github.com/klauspost/compress/fse#Compress) function.
You must provide input and will receive the output and maybe an error.
These error values can be returned:
| Error | Description |
|---------------------|-----------------------------------------------------------------------------|
| `<nil>` | Everything ok, output is returned |
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
| `(error)` | An internal error occurred. |
As can be seen above there are errors that will be returned even under normal operation so it is important to handle these.
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/fse#Scratch) object
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
object can be used for both.
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
Decompressing is done by calling the [`Decompress`](https://godoc.org/github.com/klauspost/compress/fse#Decompress) function.
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
your input was likely corrupted.
It is important to note that a successful decoding does *not* mean your output matches your original input.
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
For more detailed usage, see examples in the [godoc documentation](https://godoc.org/github.com/klauspost/compress/fse#pkg-examples).
# Performance
A lot of factors are affecting speed. Block sizes and compressibility of the material are primary factors.
All compression functions are currently only running on the calling goroutine so only one core will be used per block.
The compressor is significantly faster if symbols are kept as small as possible. The highest byte value of the input
is used to reduce some of the processing, so if all your input is above byte value 64 for instance, it may be
beneficial to transpose all your input values down by 64.
With moderate block sizes around 64k speed are typically 200MB/s per core for compression and
around 300MB/s decompression speed.
The same hardware typically does Huffman (deflate) encoding at 125MB/s and decompression at 100MB/s.
# Plans
At one point, more internals will be exposed to facilitate more "expert" usage of the components.
A streaming interface is also likely to be implemented. Likely compatible with [FSE stream format](https://github.com/Cyan4973/FiniteStateEntropy/blob/dev/programs/fileio.c#L261).
# Contributing
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
# Finite State Entropy
This package provides Finite State Entropy encoding and decoding.
Finite State Entropy (also referenced as [tANS](https://en.wikipedia.org/wiki/Asymmetric_numeral_systems#tANS))
encoding provides a fast near-optimal symbol encoding/decoding
for byte blocks as implemented in [zstandard](https://github.com/facebook/zstd).
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/fse)
## News
* Feb 2018: First implementation released. Consider this beta software for now.
# Usage
This package provides a low level interface that allows to compress single independent blocks.
Each block is separate, and there is no built in integrity checks.
This means that the caller should keep track of block sizes and also do checksums if needed.
Compressing a block is done via the [`Compress`](https://godoc.org/github.com/klauspost/compress/fse#Compress) function.
You must provide input and will receive the output and maybe an error.
These error values can be returned:
| Error | Description |
|---------------------|-----------------------------------------------------------------------------|
| `<nil>` | Everything ok, output is returned |
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
| `(error)` | An internal error occurred. |
As can be seen above there are errors that will be returned even under normal operation so it is important to handle these.
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/fse#Scratch) object
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
object can be used for both.
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
Decompressing is done by calling the [`Decompress`](https://godoc.org/github.com/klauspost/compress/fse#Decompress) function.
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
your input was likely corrupted.
It is important to note that a successful decoding does *not* mean your output matches your original input.
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
For more detailed usage, see examples in the [godoc documentation](https://godoc.org/github.com/klauspost/compress/fse#pkg-examples).
# Performance
A lot of factors are affecting speed. Block sizes and compressibility of the material are primary factors.
All compression functions are currently only running on the calling goroutine so only one core will be used per block.
The compressor is significantly faster if symbols are kept as small as possible. The highest byte value of the input
is used to reduce some of the processing, so if all your input is above byte value 64 for instance, it may be
beneficial to transpose all your input values down by 64.
With moderate block sizes around 64k speed are typically 200MB/s per core for compression and
around 300MB/s decompression speed.
The same hardware typically does Huffman (deflate) encoding at 125MB/s and decompression at 100MB/s.
# Plans
At one point, more internals will be exposed to facilitate more "expert" usage of the components.
A streaming interface is also likely to be implemented. Likely compatible with [FSE stream format](https://github.com/Cyan4973/FiniteStateEntropy/blob/dev/programs/fileio.c#L261).
# Contributing
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
changes will likely not be accepted. If in doubt open an issue before writing the PR.

View File

@@ -1,87 +1,87 @@
# Huff0 entropy compression
This package provides Huff0 encoding and decoding as used in zstd.
[Huff0](https://github.com/Cyan4973/FiniteStateEntropy#new-generation-entropy-coders),
a Huffman codec designed for modern CPU, featuring OoO (Out of Order) operations on multiple ALU
(Arithmetic Logic Unit), achieving extremely fast compression and decompression speeds.
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/huff0)
THIS PACKAGE IS NOT CONSIDERED STABLE AND API OR ENCODING MAY CHANGE IN THE FUTURE.
## News
* Mar 2018: First implementation released. Consider this beta software for now.
# Usage
This package provides a low level interface that allows to compress single independent blocks.
Each block is separate, and there is no built in integrity checks.
This means that the caller should keep track of block sizes and also do checksums if needed.
Compressing a block is done via the [`Compress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress1X) and
[`Compress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress4X) functions.
You must provide input and will receive the output and maybe an error.
These error values can be returned:
| Error | Description |
|---------------------|-----------------------------------------------------------------------------|
| `<nil>` | Everything ok, output is returned |
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
| `ErrTooBig` | Returned if the input block exceeds the maximum allowed size (128 Kib) |
| `(error)` | An internal error occurred. |
As can be seen above some of there are errors that will be returned even under normal operation so it is important to handle these.
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
object can be used for both.
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
The `Scratch` object will retain state that allows to re-use previous tables for encoding and decoding.
## Tables and re-use
Huff0 allows for reusing tables from the previous block to save space if that is expected to give better/faster results.
The Scratch object allows you to set a [`ReusePolicy`](https://godoc.org/github.com/klauspost/compress/huff0#ReusePolicy)
that controls this behaviour. See the documentation for details. This can be altered between each block.
Do however note that this information is *not* stored in the output block and it is up to the users of the package to
record whether [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable) should be called,
based on the boolean reported back from the CompressXX call.
If you want to store the table separate from the data, you can access them as `OutData` and `OutTable` on the
[`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object.
## Decompressing
The first part of decoding is to initialize the decoding table through [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable).
This will initialize the decoding tables.
You can supply the complete block to `ReadTable` and it will return the data part of the block
which can be given to the decompressor.
Decompressing is done by calling the [`Decompress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress1X)
or [`Decompress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress4X) function.
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
your input was likely corrupted.
It is important to note that a successful decoding does *not* mean your output matches your original input.
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
# Contributing
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
# Huff0 entropy compression
This package provides Huff0 encoding and decoding as used in zstd.
[Huff0](https://github.com/Cyan4973/FiniteStateEntropy#new-generation-entropy-coders),
a Huffman codec designed for modern CPU, featuring OoO (Out of Order) operations on multiple ALU
(Arithmetic Logic Unit), achieving extremely fast compression and decompression speeds.
This can be used for compressing input with a lot of similar input values to the smallest number of bytes.
This does not perform any multi-byte [dictionary coding](https://en.wikipedia.org/wiki/Dictionary_coder) as LZ coders,
but it can be used as a secondary step to compressors (like Snappy) that does not do entropy encoding.
* [Godoc documentation](https://godoc.org/github.com/klauspost/compress/huff0)
THIS PACKAGE IS NOT CONSIDERED STABLE AND API OR ENCODING MAY CHANGE IN THE FUTURE.
## News
* Mar 2018: First implementation released. Consider this beta software for now.
# Usage
This package provides a low level interface that allows to compress single independent blocks.
Each block is separate, and there is no built in integrity checks.
This means that the caller should keep track of block sizes and also do checksums if needed.
Compressing a block is done via the [`Compress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress1X) and
[`Compress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Compress4X) functions.
You must provide input and will receive the output and maybe an error.
These error values can be returned:
| Error | Description |
|---------------------|-----------------------------------------------------------------------------|
| `<nil>` | Everything ok, output is returned |
| `ErrIncompressible` | Returned when input is judged to be too hard to compress |
| `ErrUseRLE` | Returned from the compressor when the input is a single byte value repeated |
| `ErrTooBig` | Returned if the input block exceeds the maximum allowed size (128 Kib) |
| `(error)` | An internal error occurred. |
As can be seen above some of there are errors that will be returned even under normal operation so it is important to handle these.
To reduce allocations you can provide a [`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object
that can be re-used for successive calls. Both compression and decompression accepts a `Scratch` object, and the same
object can be used for both.
Be aware, that when re-using a `Scratch` object that the *output* buffer is also re-used, so if you are still using this
you must set the `Out` field in the scratch to nil. The same buffer is used for compression and decompression output.
The `Scratch` object will retain state that allows to re-use previous tables for encoding and decoding.
## Tables and re-use
Huff0 allows for reusing tables from the previous block to save space if that is expected to give better/faster results.
The Scratch object allows you to set a [`ReusePolicy`](https://godoc.org/github.com/klauspost/compress/huff0#ReusePolicy)
that controls this behaviour. See the documentation for details. This can be altered between each block.
Do however note that this information is *not* stored in the output block and it is up to the users of the package to
record whether [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable) should be called,
based on the boolean reported back from the CompressXX call.
If you want to store the table separate from the data, you can access them as `OutData` and `OutTable` on the
[`Scratch`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch) object.
## Decompressing
The first part of decoding is to initialize the decoding table through [`ReadTable`](https://godoc.org/github.com/klauspost/compress/huff0#ReadTable).
This will initialize the decoding tables.
You can supply the complete block to `ReadTable` and it will return the data part of the block
which can be given to the decompressor.
Decompressing is done by calling the [`Decompress1X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress1X)
or [`Decompress4X`](https://godoc.org/github.com/klauspost/compress/huff0#Scratch.Decompress4X) function.
You must provide the output from the compression stage, at exactly the size you got back. If you receive an error back
your input was likely corrupted.
It is important to note that a successful decoding does *not* mean your output matches your original input.
There are no integrity checks, so relying on errors from the decompressor does not assure your data is valid.
# Contributing
Contributions are always welcome. Be aware that adding public functions will require good justification and breaking
changes will likely not be accepted. If in doubt open an issue before writing the PR.

View File

@@ -1,8 +1,8 @@
language: go
go:
- 1.5
- 1.5.1
- 1.6.2
os: linux
language: go
go:
- 1.5
- 1.5.1
- 1.6.2
os: linux

View File

@@ -1,114 +1,114 @@
JsonPath
----------------
![Build Status](https://travis-ci.org/oliveagle/jsonpath.svg?branch=master)
A golang implementation of JsonPath syntax.
follow the majority rules in http://goessner.net/articles/JsonPath/
but also with some minor differences.
this library is till bleeding edge, so use it at your own risk. :D
**Golang Version Required**: 1.5+
Get Started
------------
```bash
go get github.com/oliveagle/jsonpath
```
example code:
```go
import (
"github.com/oliveagle/jsonpath"
"encoding/json"
)
var json_data interface{}
json.Unmarshal([]byte(data), &json_data)
res, err := jsonpath.JsonPathLookup(json_data, "$.expensive")
//or reuse lookup pattern
pat, _ := jsonpath.Compile(`$.store.book[?(@.price < $.expensive)].price`)
res, err := pat.Lookup(json_data)
```
Operators
--------
referenced from github.com/jayway/JsonPath
| Operator | Supported | Description |
| ---- | :---: | ---------- |
| $ | Y | The root element to query. This starts all path expressions. |
| @ | Y | The current node being processed by a filter predicate. |
| * | X | Wildcard. Available anywhere a name or numeric are required. |
| .. | X | Deep scan. Available anywhere a name is required. |
| .<name> | Y | Dot-notated child |
| ['<name>' (, '<name>')] | X | Bracket-notated child or children |
| [<number> (, <number>)] | Y | Array index or indexes |
| [start:end] | Y | Array slice operator |
| [?(<expression>)] | Y | Filter expression. Expression must evaluate to a boolean value. |
Examples
--------
given these example data.
```javascript
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}
```
example json path syntax.
----
| jsonpath | result|
| :--------- | :-------|
| $.expensive | 10|
| $.store.book[0].price | 8.95|
| $.store.book[-1].isbn | "0-395-19395-8"|
| $.store.book[0,1].price | [8.95, 12.99] |
| $.store.book[0:2].price | [8.95, 12.99, 8.99]|
| $.store.book[?(@.isbn)].price | [8.99, 22.99] |
| $.store.book[?(@.price > 10)].title | ["Sword of Honour", "The Lord of the Rings"]|
| $.store.book[?(@.price < $.expensive)].price | [8.95, 8.99] |
| $.store.book[:].price | [8.9.5, 12.99, 8.9.9, 22.99] |
| $.store.book[?(@.author =~ /(?i).*REES/)].author | "Nigel Rees" |
JsonPath
----------------
![Build Status](https://travis-ci.org/oliveagle/jsonpath.svg?branch=master)
A golang implementation of JsonPath syntax.
follow the majority rules in http://goessner.net/articles/JsonPath/
but also with some minor differences.
this library is till bleeding edge, so use it at your own risk. :D
**Golang Version Required**: 1.5+
Get Started
------------
```bash
go get github.com/oliveagle/jsonpath
```
example code:
```go
import (
"github.com/oliveagle/jsonpath"
"encoding/json"
)
var json_data interface{}
json.Unmarshal([]byte(data), &json_data)
res, err := jsonpath.JsonPathLookup(json_data, "$.expensive")
//or reuse lookup pattern
pat, _ := jsonpath.Compile(`$.store.book[?(@.price < $.expensive)].price`)
res, err := pat.Lookup(json_data)
```
Operators
--------
referenced from github.com/jayway/JsonPath
| Operator | Supported | Description |
| ---- | :---: | ---------- |
| $ | Y | The root element to query. This starts all path expressions. |
| @ | Y | The current node being processed by a filter predicate. |
| * | X | Wildcard. Available anywhere a name or numeric are required. |
| .. | X | Deep scan. Available anywhere a name is required. |
| .<name> | Y | Dot-notated child |
| ['<name>' (, '<name>')] | X | Bracket-notated child or children |
| [<number> (, <number>)] | Y | Array index or indexes |
| [start:end] | Y | Array slice operator |
| [?(<expression>)] | Y | Filter expression. Expression must evaluate to a boolean value. |
Examples
--------
given these example data.
```javascript
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}
```
example json path syntax.
----
| jsonpath | result|
| :--------- | :-------|
| $.expensive | 10|
| $.store.book[0].price | 8.95|
| $.store.book[-1].isbn | "0-395-19395-8"|
| $.store.book[0,1].price | [8.95, 12.99] |
| $.store.book[0:2].price | [8.95, 12.99, 8.99]|
| $.store.book[?(@.isbn)].price | [8.99, 22.99] |
| $.store.book[?(@.price > 10)].title | ["Sword of Honour", "The Lord of the Rings"]|
| $.store.book[?(@.price < $.expensive)].price | [8.95, 8.99] |
| $.store.book[:].price | [8.9.5, 12.99, 8.9.9, 22.99] |
| $.store.book[?(@.author =~ /(?i).*REES/)].author | "Nigel Rees" |
> Note: golang support regular expression flags in form of `(?imsU)pattern`

32
vendor/modules.txt vendored
View File

@@ -295,7 +295,7 @@ github.com/felixge/httpsnoop
# github.com/form3tech-oss/jwt-go v3.2.3+incompatible => github.com/form3tech-oss/jwt-go v3.2.2+incompatible
## explicit
github.com/form3tech-oss/jwt-go
# github.com/fsnotify/fsnotify v1.5.4 => github.com/fsnotify/fsnotify v1.4.9
# github.com/fsnotify/fsnotify v1.6.0 => github.com/fsnotify/fsnotify v1.4.9
## explicit; go 1.13
github.com/fsnotify/fsnotify
# github.com/ghodss/yaml v1.0.0 => github.com/ghodss/yaml v1.0.0
@@ -370,7 +370,7 @@ github.com/go-resty/resty/v2
# github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 => github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0
## explicit; go 1.13
github.com/go-task/slim-sprig
# github.com/gobuffalo/flect v0.2.5 => github.com/gobuffalo/flect v0.2.0
# github.com/gobuffalo/flect v0.3.0 => github.com/gobuffalo/flect v0.2.0
## explicit; go 1.12
github.com/gobuffalo/flect
# github.com/gobuffalo/genny v0.1.1 => github.com/gobuffalo/genny v0.1.1
@@ -428,7 +428,7 @@ github.com/google/gnostic/extensions
github.com/google/gnostic/jsonschema
github.com/google/gnostic/openapiv2
github.com/google/gnostic/openapiv3
# github.com/google/go-cmp v0.5.8 => github.com/google/go-cmp v0.4.0
# github.com/google/go-cmp v0.5.9 => github.com/google/go-cmp v0.4.0
## explicit; go 1.8
github.com/google/go-cmp/cmp
github.com/google/go-cmp/cmp/internal/diff
@@ -695,7 +695,7 @@ github.com/onsi/ginkgo/reporters/stenographer
github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable
github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty
github.com/onsi/ginkgo/types
# github.com/onsi/gomega v1.20.1 => github.com/onsi/gomega v1.10.1
# github.com/onsi/gomega v1.24.2 => github.com/onsi/gomega v1.10.1
## explicit
github.com/onsi/gomega
github.com/onsi/gomega/format
@@ -1209,7 +1209,7 @@ golang.org/x/crypto/ssh/knownhosts
## explicit
golang.org/x/lint
golang.org/x/lint/golint
# golang.org/x/net v0.0.0-20220909164309-bea034e7d591 => golang.org/x/net v0.0.0-20210525063256-abc453219eb5
# golang.org/x/net v0.4.0 => golang.org/x/net v0.0.0-20210525063256-abc453219eb5
## explicit; go 1.17
golang.org/x/net/context
golang.org/x/net/context/ctxhttp
@@ -1235,7 +1235,7 @@ golang.org/x/oauth2/internal
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
golang.org/x/sync/singleflight
# golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 => golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d
# golang.org/x/sys v0.3.0 => golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d
## explicit; go 1.17
golang.org/x/sys/cpu
golang.org/x/sys/internal/unsafeheader
@@ -1247,7 +1247,7 @@ golang.org/x/sys/windows/svc/eventlog
# golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 => golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1
## explicit; go 1.11
golang.org/x/term
# golang.org/x/text v0.3.7 => golang.org/x/text v0.3.0
# golang.org/x/text v0.5.0 => golang.org/x/text v0.3.0
## explicit
golang.org/x/text/encoding
golang.org/x/text/encoding/charmap
@@ -1271,7 +1271,7 @@ golang.org/x/text/width
# golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 => golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
## explicit
golang.org/x/time/rate
# golang.org/x/tools v0.1.12 => golang.org/x/tools v0.0.0-20190710153321-831012c29e42
# golang.org/x/tools v0.4.0 => golang.org/x/tools v0.0.0-20190710153321-831012c29e42
## explicit; go 1.11
golang.org/x/tools/go/analysis
golang.org/x/tools/go/analysis/passes/inspect
@@ -1614,7 +1614,7 @@ istio.io/client-go/pkg/listers/security/v1beta1
## explicit; go 1.12
istio.io/gogo-genproto/googleapis/google/api
istio.io/gogo-genproto/googleapis/google/rpc
# k8s.io/api v0.25.3 => k8s.io/api v0.25.3
# k8s.io/api v0.26.0 => k8s.io/api v0.25.3
## explicit; go 1.19
k8s.io/api/admission/v1
k8s.io/api/admission/v1beta1
@@ -1665,7 +1665,7 @@ k8s.io/api/scheduling/v1beta1
k8s.io/api/storage/v1
k8s.io/api/storage/v1alpha1
k8s.io/api/storage/v1beta1
# k8s.io/apiextensions-apiserver v0.25.3 => k8s.io/apiextensions-apiserver v0.25.3
# k8s.io/apiextensions-apiserver v0.26.0 => k8s.io/apiextensions-apiserver v0.25.3
## explicit; go 1.19
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
@@ -1684,7 +1684,7 @@ k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensio
k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/internalinterfaces
k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1
k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1beta1
# k8s.io/apimachinery v0.25.3 => k8s.io/apimachinery v0.25.3
# k8s.io/apimachinery v0.26.0 => k8s.io/apimachinery v0.25.3
## explicit; go 1.19
k8s.io/apimachinery/pkg/api/equality
k8s.io/apimachinery/pkg/api/errors
@@ -2228,7 +2228,7 @@ k8s.io/gengo/generator
k8s.io/gengo/namer
k8s.io/gengo/parser
k8s.io/gengo/types
# k8s.io/klog/v2 v2.70.1 => k8s.io/klog/v2 v2.70.1
# k8s.io/klog/v2 v2.80.1 => k8s.io/klog/v2 v2.70.1
## explicit; go 1.13
k8s.io/klog/v2
k8s.io/klog/v2/internal/buffer
@@ -2283,7 +2283,7 @@ k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1alpha1
k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1alpha1/fake
k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1
k8s.io/metrics/pkg/client/clientset/versioned/typed/metrics/v1beta1/fake
# k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed => k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
# k8s.io/utils v0.0.0-20221107191617-1a15be271d1d => k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
## explicit; go 1.12
k8s.io/utils/buffer
k8s.io/utils/clock
@@ -2414,8 +2414,8 @@ sigs.k8s.io/controller-runtime/pkg/webhook
sigs.k8s.io/controller-runtime/pkg/webhook/admission
sigs.k8s.io/controller-runtime/pkg/webhook/conversion
sigs.k8s.io/controller-runtime/pkg/webhook/internal/metrics
# sigs.k8s.io/controller-tools v0.9.0 => sigs.k8s.io/controller-tools v0.6.2
## explicit; go 1.16
# sigs.k8s.io/controller-tools v0.11.1 => sigs.k8s.io/controller-tools v0.11.1
## explicit; go 1.19
sigs.k8s.io/controller-tools/cmd/controller-gen
sigs.k8s.io/controller-tools/pkg/crd
sigs.k8s.io/controller-tools/pkg/crd/markers
@@ -3341,7 +3341,7 @@ sigs.k8s.io/yaml
# sigs.k8s.io/apiserver-network-proxy/konnectivity-client => sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.33
# sigs.k8s.io/application => sigs.k8s.io/application v0.8.4-0.20201016185654-c8e2959e57a0
# sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.12.1
# sigs.k8s.io/controller-tools => sigs.k8s.io/controller-tools v0.6.2
# sigs.k8s.io/controller-tools => sigs.k8s.io/controller-tools v0.11.1
# sigs.k8s.io/json => sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2
# sigs.k8s.io/kind => sigs.k8s.io/kind v0.8.1
# sigs.k8s.io/kubebuilder/v3 => sigs.k8s.io/kubebuilder/v3 v3.0.0-alpha.0.0.20210716121009-fde793f20067

View File

@@ -5,7 +5,7 @@ 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
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,

View File

@@ -6,7 +6,6 @@ import (
apiextinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
@@ -22,13 +21,15 @@ func init() {
if err := apiext.AddToScheme(conversionScheme); err != nil {
panic("must be able to add apiextensions/v1 to the CRD conversion Scheme")
}
if err := apiextv1beta1.AddToScheme(conversionScheme); err != nil {
panic("must be able to add apiextensions/v1beta1 to the CRD conversion Scheme")
}
}
// AsVersion converts a CRD from the canonical internal form (currently v1) to some external form.
func AsVersion(original apiext.CustomResourceDefinition, gv schema.GroupVersion) (runtime.Object, error) {
// TODO: Do we need to keep maintaining this conversion function
// post 1.22 when only CRDv1 is served by the apiserver?
if gv == apiextv1beta1.SchemeGroupVersion {
return nil, fmt.Errorf("apiVersion %q is not supported", gv.String())
}
// We can use the internal versions an existing conversions from kubernetes, since they're not in k/k itself.
// This punts the problem of conversion down the road for a future maintainer (or future instance of @directxman12)
// when we have to support older versions that get removed, or when API machinery decides to yell at us for this
@@ -40,83 +41,3 @@ func AsVersion(original apiext.CustomResourceDefinition, gv schema.GroupVersion)
return conversionScheme.ConvertToVersion(intVer, gv)
}
// mergeIdenticalSubresources checks to see if subresources are identical across
// all versions, and if so, merges them into a top-level version.
//
// This assumes you're not using trivial versions.
func mergeIdenticalSubresources(crd *apiextv1beta1.CustomResourceDefinition) {
subres := crd.Spec.Versions[0].Subresources
for _, ver := range crd.Spec.Versions {
if ver.Subresources == nil || !equality.Semantic.DeepEqual(subres, ver.Subresources) {
// either all nil, or not identical
return
}
}
// things are identical if we've gotten this far, so move the subresources up
// and discard the identical per-version ones
crd.Spec.Subresources = subres
for i := range crd.Spec.Versions {
crd.Spec.Versions[i].Subresources = nil
}
}
// mergeIdenticalSchemata checks to see if schemata are identical across
// all versions, and if so, merges them into a top-level version.
//
// This assumes you're not using trivial versions.
func mergeIdenticalSchemata(crd *apiextv1beta1.CustomResourceDefinition) {
schema := crd.Spec.Versions[0].Schema
for _, ver := range crd.Spec.Versions {
if ver.Schema == nil || !equality.Semantic.DeepEqual(schema, ver.Schema) {
// either all nil, or not identical
return
}
}
// things are identical if we've gotten this far, so move the schemata up
// to a single schema and discard the identical per-version ones
crd.Spec.Validation = schema
for i := range crd.Spec.Versions {
crd.Spec.Versions[i].Schema = nil
}
}
// mergeIdenticalPrinterColumns checks to see if schemata are identical across
// all versions, and if so, merges them into a top-level version.
//
// This assumes you're not using trivial versions.
func mergeIdenticalPrinterColumns(crd *apiextv1beta1.CustomResourceDefinition) {
cols := crd.Spec.Versions[0].AdditionalPrinterColumns
for _, ver := range crd.Spec.Versions {
if len(ver.AdditionalPrinterColumns) == 0 || !equality.Semantic.DeepEqual(cols, ver.AdditionalPrinterColumns) {
// either all nil, or not identical
return
}
}
// things are identical if we've gotten this far, so move the printer columns up
// and discard the identical per-version ones
crd.Spec.AdditionalPrinterColumns = cols
for i := range crd.Spec.Versions {
crd.Spec.Versions[i].AdditionalPrinterColumns = nil
}
}
// MergeIdenticalVersionInfo makes sure that components of the Versions field that are identical
// across all versions get merged into the top-level fields in v1beta1.
//
// This is required by the Kubernetes API server validation.
//
// The reason is that a v1beta1 -> v1 -> v1beta1 conversion cycle would need to
// round-trip identically, v1 doesn't have top-level subresources, and without
// this restriction it would be ambiguous how a v1-with-identical-subresources
// converts into a v1beta1).
func MergeIdenticalVersionInfo(crd *apiextv1beta1.CustomResourceDefinition) {
if len(crd.Spec.Versions) > 0 {
mergeIdenticalSubresources(crd)
mergeIdenticalSchemata(crd)
mergeIdenticalPrinterColumns(crd)
}
}

View File

@@ -17,14 +17,14 @@ limitations under the License.
// Package crd contains utilities for generating CustomResourceDefinitions and
// their corresponding OpenAPI validation schemata.
//
// Markers
// # Markers
//
// Markers live under the markers subpackage. Two types of markers exist:
// those that modify schema generation (for validation), and those that modify
// the rest of the CRD. See the subpackage for more information and all
// supported markers.
//
// Collecting Types and Generating CRDs
// # Collecting Types and Generating CRDs
//
// The Parser is the entrypoint for collecting the information required to
// generate CRDs. Like loader and collector, its methods are idemptotent, not
@@ -40,13 +40,13 @@ limitations under the License.
// Errors are generally attached directly to the relevant Package with
// AddError.
//
// Known Packages
// # Known Packages
//
// There are a few types from Kubernetes that have special meaning, but don't
// have validation markers attached. Those specific types have overrides
// listed in KnownPackages that can be added as overrides to any parser.
//
// Flattening
// # Flattening
//
// Once schemata are generated, they can be used directly by external tooling
// (like JSONSchema validators), but must first be "flattened" to not contain

View File

@@ -143,6 +143,10 @@ func flattenAllOfInto(dst *apiext.JSONSchemaProps, src apiext.JSONSchemaProps, e
dstProps.Schema = &apiext.JSONSchemaProps{}
}
flattenAllOfInto(dstProps.Schema, *srcProps.Schema, errRec)
case "XPreserveUnknownFields":
dstField.Set(srcField)
case "XMapType":
dstField.Set(srcField)
// NB(directxman12): no need to explicitly handle nullable -- false is considered to be the zero value
// TODO(directxman12): src isn't necessarily the field value -- it's just the most recent allOf entry
default:

View File

@@ -20,10 +20,9 @@ import (
"fmt"
"go/ast"
"go/types"
"os"
"sort"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextlegacy "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
crdmarkers "sigs.k8s.io/controller-tools/pkg/crd/markers"
@@ -33,30 +32,20 @@ import (
"sigs.k8s.io/controller-tools/pkg/version"
)
// The identifier for v1 CustomResourceDefinitions.
const v1 = "v1"
// The default CustomResourceDefinition version to generate.
const defaultVersion = "v1"
const defaultVersion = v1
// +controllertools:marker:generateHelp
// Generator generates CustomResourceDefinition objects.
type Generator struct {
// TrivialVersions indicates that we should produce a single-version CRD.
// IgnoreUnexportedFields indicates that we should skip unexported fields.
//
// Single "trivial-version" CRDs are compatible with older (pre 1.13)
// Kubernetes API servers. The storage version's schema will be used as
// the CRD's schema.
//
// Only works with the v1beta1 CRD version.
TrivialVersions bool `marker:",optional"`
// PreserveUnknownFields indicates whether or not we should turn off pruning.
//
// Left unspecified, it'll default to true when only a v1beta1 CRD is
// generated (to preserve compatibility with older versions of this tool),
// or false otherwise.
//
// It's required to be false for v1 CRDs.
PreserveUnknownFields *bool `marker:",optional"`
// Left unspecified, the default is false.
IgnoreUnexportedFields *bool `marker:",optional"`
// AllowDangerousTypes allows types which are usually omitted from CRD generation
// because they are not recommended.
@@ -78,6 +67,8 @@ type Generator struct {
// CRDVersions specifies the target API versions of the CRD type itself to
// generate. Defaults to v1.
//
// Currently, the only supported value is v1.
//
// The first version listed will be assumed to be the "default" version and
// will not get a version suffix in the output filename.
//
@@ -95,12 +86,20 @@ func (Generator) CheckFilter() loader.NodeFilter {
func (Generator) RegisterMarkers(into *markers.Registry) error {
return crdmarkers.Register(into)
}
// transformRemoveCRDStatus ensures we do not write the CRD status field.
func transformRemoveCRDStatus(obj map[string]interface{}) error {
delete(obj, "status")
return nil
}
func (g Generator) Generate(ctx *genall.GenerationContext) error {
parser := &Parser{
Collector: ctx.Collector,
Checker: ctx.Checker,
// Perform defaulting here to avoid ambiguity later
AllowDangerousTypes: g.AllowDangerousTypes != nil && *g.AllowDangerousTypes == true,
IgnoreUnexportedFields: g.IgnoreUnexportedFields != nil && *g.IgnoreUnexportedFields == true,
AllowDangerousTypes: g.AllowDangerousTypes != nil && *g.AllowDangerousTypes == true,
// Indicates the parser on whether to register the ObjectMeta type or not
GenerateEmbeddedObjectMeta: g.GenerateEmbeddedObjectMeta != nil && *g.GenerateEmbeddedObjectMeta == true,
}
@@ -129,7 +128,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
crdVersions = []string{defaultVersion}
}
for groupKind := range kubeKinds {
for _, groupKind := range kubeKinds {
parser.NeedCRDFor(groupKind, g.MaxDescLen)
crdRaw := parser.CustomResourceDefinitions[groupKind]
addAttribution(&crdRaw)
@@ -146,45 +145,15 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
versionedCRDs[i] = conv
}
if g.TrivialVersions {
for i, crd := range versionedCRDs {
if crdVersions[i] == "v1beta1" {
toTrivialVersions(crd.(*apiextlegacy.CustomResourceDefinition))
}
}
}
// *If* we're only generating v1beta1 CRDs, default to `preserveUnknownFields: (unset)`
// for compatibility purposes. In any other case, default to false, since that's
// the sensible default and is required for v1.
v1beta1Only := len(crdVersions) == 1 && crdVersions[0] == "v1beta1"
switch {
case (g.PreserveUnknownFields == nil || *g.PreserveUnknownFields) && v1beta1Only:
crd := versionedCRDs[0].(*apiextlegacy.CustomResourceDefinition)
crd.Spec.PreserveUnknownFields = nil
case g.PreserveUnknownFields == nil, g.PreserveUnknownFields != nil && !*g.PreserveUnknownFields:
// it'll be false here (coming from v1) -- leave it as such
default:
return fmt.Errorf("you may only set PreserveUnknownFields to true with v1beta1 CRDs")
}
for i, crd := range versionedCRDs {
// defaults are not allowed to be specified in v1beta1 CRDs and
// decriptions are not allowed on the metadata regardless of version
// strip them before writing to a file
if crdVersions[i] == "v1beta1" {
removeDefaultsFromSchemas(crd.(*apiextlegacy.CustomResourceDefinition))
removeDescriptionFromMetadataLegacy(crd.(*apiextlegacy.CustomResourceDefinition))
} else {
removeDescriptionFromMetadata(crd.(*apiext.CustomResourceDefinition))
}
removeDescriptionFromMetadata(crd.(*apiext.CustomResourceDefinition))
var fileName string
if i == 0 {
fileName = fmt.Sprintf("%s_%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural)
} else {
fileName = fmt.Sprintf("%s_%s.%s.yaml", crdRaw.Spec.Group, crdRaw.Spec.Names.Plural, crdVersions[i])
}
if err := ctx.WriteYAML(fileName, crd); err != nil {
if err := ctx.WriteYAML(fileName, []interface{}{crd}, genall.WithTransform(transformRemoveCRDStatus)); err != nil {
return err
}
}
@@ -212,71 +181,6 @@ func removeDescriptionFromMetadataProps(v *apiext.JSONSchemaProps) {
}
}
func removeDescriptionFromMetadataLegacy(crd *apiextlegacy.CustomResourceDefinition) {
if crd.Spec.Validation != nil {
removeDescriptionFromMetadataPropsLegacy(crd.Spec.Validation.OpenAPIV3Schema)
}
for _, versionSpec := range crd.Spec.Versions {
if versionSpec.Schema != nil {
removeDescriptionFromMetadataPropsLegacy(versionSpec.Schema.OpenAPIV3Schema)
}
}
}
func removeDescriptionFromMetadataPropsLegacy(v *apiextlegacy.JSONSchemaProps) {
if m, ok := v.Properties["metadata"]; ok {
meta := &m
if meta.Description != "" {
meta.Description = ""
v.Properties["metadata"] = m
}
}
}
// removeDefaultsFromSchemas will remove all instances of default values being
// specified across all defined API versions
func removeDefaultsFromSchemas(crd *apiextlegacy.CustomResourceDefinition) {
if crd.Spec.Validation != nil {
removeDefaultsFromSchemaProps(crd.Spec.Validation.OpenAPIV3Schema)
}
for _, versionSpec := range crd.Spec.Versions {
if versionSpec.Schema != nil {
removeDefaultsFromSchemaProps(versionSpec.Schema.OpenAPIV3Schema)
}
}
}
// removeDefaultsFromSchemaProps will recurse into JSONSchemaProps to remove
// all instances of default values being specified
func removeDefaultsFromSchemaProps(v *apiextlegacy.JSONSchemaProps) {
if v == nil {
return
}
if v.Default != nil {
fmt.Fprintln(os.Stderr, "Warning: default unsupported in CRD version v1beta1, v1 required. Removing defaults.")
}
// nil-out the default field
v.Default = nil
for name, prop := range v.Properties {
// iter var reference is fine -- we handle the persistence of the modfications on the line below
//nolint:gosec
removeDefaultsFromSchemaProps(&prop)
v.Properties[name] = prop
}
if v.Items != nil {
removeDefaultsFromSchemaProps(v.Items.Schema)
for i := range v.Items.JSONSchemas {
props := v.Items.JSONSchemas[i]
removeDefaultsFromSchemaProps(&props)
v.Items.JSONSchemas[i] = props
}
}
}
// FixTopLevelMetadata resets the schema for the top-level metadata field which is needed for CRD validation
func FixTopLevelMetadata(crd apiext.CustomResourceDefinition) {
for _, v := range crd.Spec.Versions {
@@ -289,32 +193,6 @@ func FixTopLevelMetadata(crd apiext.CustomResourceDefinition) {
}
}
// toTrivialVersions strips out all schemata except for the storage schema,
// and moves that up into the root object. This makes the CRD compatible
// with pre 1.13 clusters.
func toTrivialVersions(crd *apiextlegacy.CustomResourceDefinition) {
var canonicalSchema *apiextlegacy.CustomResourceValidation
var canonicalSubresources *apiextlegacy.CustomResourceSubresources
var canonicalColumns []apiextlegacy.CustomResourceColumnDefinition
for i, ver := range crd.Spec.Versions {
if ver.Storage == true {
canonicalSchema = ver.Schema
canonicalSubresources = ver.Subresources
canonicalColumns = ver.AdditionalPrinterColumns
}
crd.Spec.Versions[i].Schema = nil
crd.Spec.Versions[i].Subresources = nil
crd.Spec.Versions[i].AdditionalPrinterColumns = nil
}
if canonicalSchema == nil {
return
}
crd.Spec.Validation = canonicalSchema
crd.Spec.Subresources = canonicalSubresources
crd.Spec.AdditionalPrinterColumns = canonicalColumns
}
// addAttribution adds attribution info to indicate controller-gen tool was used
// to generate this CRD definition along with the version info.
func addAttribution(crd *apiext.CustomResourceDefinition) {
@@ -339,7 +217,7 @@ func FindMetav1(roots []*loader.Package) *loader.Package {
// FindKubeKinds locates all types that contain TypeMeta and ObjectMeta
// (and thus may be a Kubernetes object), and returns the corresponding
// group-kinds.
func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) map[schema.GroupKind]struct{} {
func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) []schema.GroupKind {
// TODO(directxman12): technically, we should be finding metav1 per-package
kubeKinds := map[schema.GroupKind]struct{}{}
for typeIdent, info := range parser.Types {
@@ -370,7 +248,12 @@ func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) map[schema.GroupKi
}
fieldPkgPath := loader.NonVendorPath(namedField.Obj().Pkg().Path())
fieldPkg := pkg.Imports()[fieldPkgPath]
if fieldPkg != metav1Pkg {
// Compare the metav1 package by ID and not by the actual instance
// of the object. The objects in memory could be different due to
// loading from different root paths, even when they both refer to
// the same metav1 package.
if fieldPkg == nil || fieldPkg.ID != metav1Pkg.ID {
continue
}
@@ -393,7 +276,15 @@ func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) map[schema.GroupKi
kubeKinds[groupKind] = struct{}{}
}
return kubeKinds
groupKindList := make([]schema.GroupKind, 0, len(kubeKinds))
for groupKind := range kubeKinds {
groupKindList = append(groupKindList, groupKind)
}
sort.Slice(groupKindList, func(i, j int) bool {
return groupKindList[i].String() < groupKindList[j].String()
})
return groupKindList
}
// filterTypesForCRDs filters out all nodes that aren't used in CRD generation,

View File

@@ -5,7 +5,7 @@ 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
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,
@@ -74,7 +74,8 @@ var KnownPackages = map[string]PackageOverride{
"k8s.io/apimachinery/pkg/runtime": func(p *Parser, pkg *loader.Package) {
p.Schemata[TypeIdent{Name: "RawExtension", Package: pkg}] = apiext.JSONSchemaProps{
// TODO(directxman12): regexp validation for this (or get kube to support it as a format value)
Type: "object",
Type: "object",
XPreserveUnknownFields: boolPtr(true),
}
p.AddPackage(pkg) // get the rest of the types
},

View File

@@ -18,6 +18,7 @@ package markers
import (
"fmt"
"strings"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -51,6 +52,9 @@ var CRDMarkers = []*definitionWithHelp{
must(markers.MakeDefinition("kubebuilder:deprecatedversion", markers.DescribesType, DeprecatedVersion{})).
WithHelp(DeprecatedVersion{}.Help()),
must(markers.MakeDefinition("kubebuilder:metadata", markers.DescribesType, Metadata{})).
WithHelp(Metadata{}.Help()),
}
// TODO: categories and singular used to be annotations types
@@ -345,3 +349,39 @@ func (s DeprecatedVersion) ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec,
}
return nil
}
// +controllertools:marker:generateHelp:category=CRD
// Metadata configures the additional annotations or labels for this CRD.
// For example adding annotation "api-approved.kubernetes.io" for a CRD with Kubernetes groups,
// or annotation "cert-manager.io/inject-ca-from-secret" for a CRD that needs CA injection.
type Metadata struct {
// Annotations will be added into the annotations of this CRD.
Annotations []string `marker:",optional"`
// Labels will be added into the labels of this CRD.
Labels []string `marker:",optional"`
}
func (s Metadata) ApplyToCRD(crd *apiext.CustomResourceDefinition, version string) error {
if len(s.Annotations) > 0 {
if crd.Annotations == nil {
crd.Annotations = map[string]string{}
}
for _, str := range s.Annotations {
kv := strings.SplitN(str, "=", 2)
crd.Annotations[kv[0]] = kv[1]
}
}
if len(s.Labels) > 0 {
if crd.Labels == nil {
crd.Labels = map[string]string{}
}
for _, str := range s.Labels {
kv := strings.SplitN(str, "=", 2)
crd.Labels[kv[0]] = kv[1]
}
}
return nil
}

View File

@@ -19,7 +19,7 @@ limitations under the License.
//
// All markers related to CRD generation live in AllDefinitions.
//
// Validation Markers
// # Validation Markers
//
// Validation markers have values that implement ApplyToSchema
// (crd.SchemaMarker). Any marker implementing this will automatically
@@ -31,7 +31,7 @@ limitations under the License.
// All validation markers start with "+kubebuilder:validation", and
// have the same name as their type name.
//
// CRD Markers
// # CRD Markers
//
// Markers that modify anything in the CRD itself *except* for the schema
// implement ApplyToCRD (crd.CRDMarker). They are expected to detect whether
@@ -39,7 +39,7 @@ limitations under the License.
// them), or to the root-level CRD for legacy cases. They are applied *after*
// the rest of the CRD is computed.
//
// Misc
// # Misc
//
// This package also defines the "+groupName" and "+versionName" package-level
// markers, for defining package<->group-version mappings.

View File

@@ -28,12 +28,20 @@ import (
var TopologyMarkers = []*definitionWithHelp{
must(markers.MakeDefinition("listMapKey", markers.DescribesField, ListMapKey(""))).
WithHelp(ListMapKey("").Help()),
must(markers.MakeDefinition("listMapKey", markers.DescribesType, ListMapKey(""))).
WithHelp(ListMapKey("").Help()),
must(markers.MakeDefinition("listType", markers.DescribesField, ListType(""))).
WithHelp(ListType("").Help()),
must(markers.MakeDefinition("listType", markers.DescribesType, ListType(""))).
WithHelp(ListType("").Help()),
must(markers.MakeDefinition("mapType", markers.DescribesField, MapType(""))).
WithHelp(MapType("").Help()),
must(markers.MakeDefinition("mapType", markers.DescribesType, MapType(""))).
WithHelp(MapType("").Help()),
must(markers.MakeDefinition("structType", markers.DescribesField, StructType(""))).
WithHelp(StructType("").Help()),
must(markers.MakeDefinition("structType", markers.DescribesType, StructType(""))).
WithHelp(StructType("").Help()),
}
func init() {
@@ -47,15 +55,15 @@ func init() {
//
// Possible data-structure types of a list are:
//
// - "map": it needs to have a key field, which will be used to build an
// associative list. A typical example is a the pod container list,
// which is indexed by the container name.
// - "map": it needs to have a key field, which will be used to build an
// associative list. A typical example is a the pod container list,
// which is indexed by the container name.
//
// - "set": Fields need to be "scalar", and there can be only one
// occurrence of each.
// - "set": Fields need to be "scalar", and there can be only one
// occurrence of each.
//
// - "atomic": All the fields in the list are treated as a single value,
// are typically manipulated together by the same actor.
// - "atomic": All the fields in the list are treated as a single value,
// are typically manipulated together by the same actor.
type ListType string
// +controllertools:marker:generateHelp:category="CRD processing"
@@ -75,12 +83,12 @@ type ListMapKey string
//
// Possible values:
//
// - "granular": items in the map are independent of each other,
// and can be manipulated by different actors.
// This is the default behavior.
// - "granular": items in the map are independent of each other,
// and can be manipulated by different actors.
// This is the default behavior.
//
// - "atomic": all fields are treated as one unit.
// Any changes have to replace the entire map.
// - "atomic": all fields are treated as one unit.
// Any changes have to replace the entire map.
type MapType string
// +controllertools:marker:generateHelp:category="CRD processing"
@@ -91,17 +99,17 @@ type MapType string
//
// Possible values:
//
// - "granular": fields in the struct are independent of each other,
// and can be manipulated by different actors.
// This is the default behavior.
// - "granular": fields in the struct are independent of each other,
// and can be manipulated by different actors.
// This is the default behavior.
//
// - "atomic": all fields are treated as one unit.
// Any changes have to replace the entire struct.
// - "atomic": all fields are treated as one unit.
// Any changes have to replace the entire struct.
type StructType string
func (l ListType) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "array" {
return fmt.Errorf("must apply listType to an array")
return fmt.Errorf("must apply listType to an array, found %s", schema.Type)
}
if l != "map" && l != "atomic" && l != "set" {
return fmt.Errorf(`ListType must be either "map", "set" or "atomic"`)
@@ -115,7 +123,7 @@ func (l ListType) ApplyFirst() {}
func (l ListMapKey) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "array" {
return fmt.Errorf("must apply listMapKey to an array")
return fmt.Errorf("must apply listMapKey to an array, found %s", schema.Type)
}
if schema.XListType == nil || *schema.XListType != "map" {
return fmt.Errorf("must apply listMapKey to an associative-list")

View File

@@ -17,9 +17,9 @@ limitations under the License.
package markers
import (
"fmt"
"encoding/json"
"fmt"
"math"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -37,7 +37,7 @@ const (
// reusable and writing complex validations on slice items.
var ValidationMarkers = mustMakeAllWithPrefix("kubebuilder:validation", markers.DescribesField,
// integer markers
// numeric markers
Maximum(0),
Minimum(0),
@@ -66,6 +66,8 @@ var ValidationMarkers = mustMakeAllWithPrefix("kubebuilder:validation", markers.
Type(""),
XPreserveUnknownFields{},
XEmbeddedResource{},
XIntOrString{},
XValidation{},
)
// FieldOnlyMarkers list field-specific validation markers (i.e. those markers that don't make
@@ -121,11 +123,19 @@ func init() {
// +controllertools:marker:generateHelp:category="CRD validation"
// Maximum specifies the maximum numeric value that this field can have.
type Maximum int
type Maximum float64
func (m Maximum) Value() float64 {
return float64(m)
}
// +controllertools:marker:generateHelp:category="CRD validation"
// Minimum specifies the minimum numeric value that this field can have. Negative integers are supported.
type Minimum int
// Minimum specifies the minimum numeric value that this field can have. Negative numbers are supported.
type Minimum float64
func (m Minimum) Value() float64 {
return float64(m)
}
// +controllertools:marker:generateHelp:category="CRD validation"
// ExclusiveMinimum indicates that the minimum is "up to" but not including that value.
@@ -137,7 +147,11 @@ type ExclusiveMaximum bool
// +controllertools:marker:generateHelp:category="CRD validation"
// MultipleOf specifies that this field must have a numeric value that's a multiple of this one.
type MultipleOf int
type MultipleOf float64
func (m MultipleOf) Value() float64 {
return float64(m)
}
// +controllertools:marker:generateHelp:category="CRD validation"
// MaxLength specifies the maximum length for this string.
@@ -232,6 +246,14 @@ type XPreserveUnknownFields struct{}
// field, yet it is possible. This can be combined with PreserveUnknownFields.
type XEmbeddedResource struct{}
// +controllertools:marker:generateHelp:category="CRD validation"
// IntOrString marks a fields as an IntOrString.
//
// This is required when applying patterns or other validations to an IntOrString
// field. Knwon information about the type is applied during the collapse phase
// and as such is not normally available during marker application.
type XIntOrString struct{}
// +controllertools:marker:generateHelp:category="CRD validation"
// Schemaless marks a field as being a schemaless object.
//
@@ -242,41 +264,80 @@ type XEmbeddedResource struct{}
// to be used only as a last resort.
type Schemaless struct{}
func hasNumericType(schema *apiext.JSONSchemaProps) bool {
return schema.Type == "integer" || schema.Type == "number"
}
func isIntegral(value float64) bool {
return value == math.Trunc(value) && !math.IsNaN(value) && !math.IsInf(value, 0)
}
// +controllertools:marker:generateHelp:category="CRD validation"
// XValidation marks a field as requiring a value for which a given
// expression evaluates to true.
//
// This marker may be repeated to specify multiple expressions, all of
// which must evaluate to true.
type XValidation struct {
Rule string
Message string `marker:",optional"`
}
func (m Maximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply maximum to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply maximum to a numeric value, found %s", schema.Type)
}
val := float64(m)
if schema.Type == "integer" && !isIntegral(m.Value()) {
return fmt.Errorf("cannot apply non-integral maximum validation (%v) to integer value", m.Value())
}
val := m.Value()
schema.Maximum = &val
return nil
}
func (m Minimum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply minimum to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply minimum to a numeric value, found %s", schema.Type)
}
val := float64(m)
if schema.Type == "integer" && !isIntegral(m.Value()) {
return fmt.Errorf("cannot apply non-integral minimum validation (%v) to integer value", m.Value())
}
val := m.Value()
schema.Minimum = &val
return nil
}
func (m ExclusiveMaximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply exclusivemaximum to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply exclusivemaximum to a numeric value, found %s", schema.Type)
}
schema.ExclusiveMaximum = bool(m)
return nil
}
func (m ExclusiveMinimum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply exclusiveminimum to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply exclusiveminimum to a numeric value, found %s", schema.Type)
}
schema.ExclusiveMinimum = bool(m)
return nil
}
func (m MultipleOf) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "integer" {
return fmt.Errorf("must apply multipleof to an integer")
if !hasNumericType(schema) {
return fmt.Errorf("must apply multipleof to a numeric value, found %s", schema.Type)
}
val := float64(m)
if schema.Type == "integer" && !isIntegral(m.Value()) {
return fmt.Errorf("cannot apply non-integral multipleof validation (%v) to integer value", m.Value())
}
val := m.Value()
schema.MultipleOf = &val
return nil
}
@@ -289,6 +350,7 @@ func (m MaxLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.MaxLength = &val
return nil
}
func (m MinLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "string" {
return fmt.Errorf("must apply minlength to a string")
@@ -297,9 +359,13 @@ func (m MinLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.MinLength = &val
return nil
}
func (m Pattern) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "string" {
return fmt.Errorf("must apply pattern to a string")
// Allow string types or IntOrStrings. An IntOrString will still
// apply the pattern validation when a string is detected, the pattern
// will not apply to ints though.
if schema.Type != "string" && !schema.XIntOrString {
return fmt.Errorf("must apply pattern to a `string` or `IntOrString`")
}
schema.Pattern = string(m)
return nil
@@ -313,6 +379,7 @@ func (m MaxItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.MaxItems = &val
return nil
}
func (m MinItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "array" {
return fmt.Errorf("must apply minitems to an array")
@@ -321,6 +388,7 @@ func (m MinItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.MinItems = &val
return nil
}
func (m UniqueItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
if schema.Type != "array" {
return fmt.Errorf("must apply uniqueitems to an array")
@@ -364,6 +432,7 @@ func (m Enum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.Enum = vals
return nil
}
func (m Format) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.Format = string(m)
return nil
@@ -406,3 +475,21 @@ func (m XEmbeddedResource) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.XEmbeddedResource = true
return nil
}
// NB(JoelSpeed): we use this property in other markers here,
// which means the "XIntOrString" marker *must* be applied first.
func (m XIntOrString) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.XIntOrString = true
return nil
}
func (m XIntOrString) ApplyFirst() {}
func (m XValidation) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
schema.XValidations = append(schema.XValidations, apiext.ValidationRule{
Rule: m.Rule,
Message: m.Message,
})
return nil
}

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
@@ -48,7 +49,7 @@ func (DeprecatedVersion) Help() *markers.DefinitionHelp {
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{
"Warning": markers.DetailedHelp{
"Warning": {
Summary: "message to be shown on the deprecated version",
Details: "",
},
@@ -116,7 +117,7 @@ func (ListType) Help() *markers.DefinitionHelp {
Category: "CRD processing",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies the type of data-structure that the list represents (map, set, atomic). ",
Details: "Possible data-structure types of a list are: \n - \"map\": it needs to have a key field, which will be used to build an associative list. A typical example is a the pod container list, which is indexed by the container name. \n - \"set\": Fields need to be \"scalar\", and there can be only one occurrence of each. \n - \"atomic\": All the fields in the list are treated as a single value, are typically manipulated together by the same actor.",
Details: "Possible data-structure types of a list are: \n - \"map\": it needs to have a key field, which will be used to build an associative list. A typical example is a the pod container list, which is indexed by the container name. \n - \"set\": Fields need to be \"scalar\", and there can be only one occurrence of each. \n - \"atomic\": All the fields in the list are treated as a single value, are typically manipulated together by the same actor.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
@@ -127,7 +128,7 @@ func (MapType) Help() *markers.DefinitionHelp {
Category: "CRD processing",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies the level of atomicity of the map; i.e. whether each item in the map is independent of the others, or all fields are treated as a single unit. ",
Details: "Possible values: \n - \"granular\": items in the map are independent of each other, and can be manipulated by different actors. This is the default behavior. \n - \"atomic\": all fields are treated as one unit. Any changes have to replace the entire map.",
Details: "Possible values: \n - \"granular\": items in the map are independent of each other, and can be manipulated by different actors. This is the default behavior. \n - \"atomic\": all fields are treated as one unit. Any changes have to replace the entire map.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
@@ -177,6 +178,26 @@ func (Maximum) Help() *markers.DefinitionHelp {
}
}
func (Metadata) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD",
DetailedHelp: markers.DetailedHelp{
Summary: "configures the additional annotations or labels for this CRD. For example adding annotation \"api-approved.kubernetes.io\" for a CRD with Kubernetes groups, or annotation \"cert-manager.io/inject-ca-from-secret\" for a CRD that needs CA injection.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{
"Annotations": {
Summary: "will be added into the annotations of this CRD.",
Details: "",
},
"Labels": {
Summary: "will be added into the labels of this CRD.",
Details: "",
},
},
}
}
func (MinItems) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
@@ -214,7 +235,7 @@ func (Minimum) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies the minimum numeric value that this field can have. Negative integers are supported.",
Summary: "specifies the minimum numeric value that this field can have. Negative numbers are supported.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
@@ -360,7 +381,7 @@ func (StructType) Help() *markers.DefinitionHelp {
Category: "CRD processing",
DetailedHelp: markers.DetailedHelp{
Summary: "specifies the level of atomicity of the struct; i.e. whether each field in the struct is independent of the others, or all fields are treated as a single unit. ",
Details: "Possible values: \n - \"granular\": fields in the struct are independent of each other, and can be manipulated by different actors. This is the default behavior. \n - \"atomic\": all fields are treated as one unit. Any changes have to replace the entire struct.",
Details: "Possible values: \n - \"granular\": fields in the struct are independent of each other, and can be manipulated by different actors. This is the default behavior. \n - \"atomic\": all fields are treated as one unit. Any changes have to replace the entire struct.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
@@ -445,6 +466,17 @@ func (XEmbeddedResource) Help() *markers.DefinitionHelp {
}
}
func (XIntOrString) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "IntOrString marks a fields as an IntOrString. ",
Details: "This is required when applying patterns or other validations to an IntOrString field. Knwon information about the type is applied during the collapse phase and as such is not normally available during marker application.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
}
func (XPreserveUnknownFields) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD processing",
@@ -455,3 +487,23 @@ func (XPreserveUnknownFields) Help() *markers.DefinitionHelp {
FieldHelp: map[string]markers.DetailedHelp{},
}
}
func (XValidation) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "CRD validation",
DetailedHelp: markers.DetailedHelp{
Summary: "marks a field as requiring a value for which a given expression evaluates to true. ",
Details: "This marker may be repeated to specify multiple expressions, all of which must evaluate to true.",
},
FieldHelp: map[string]markers.DetailedHelp{
"Rule": {
Summary: "",
Details: "",
},
"Message": {
Summary: "",
Details: "",
},
},
}
}

View File

@@ -87,6 +87,9 @@ type Parser struct {
// TODO: Should we have a more formal mechanism for putting "type patterns" in each of the above categories?
AllowDangerousTypes bool
// IgnoreUnexportedFields specifies if unexported fields on the struct should be skipped
IgnoreUnexportedFields bool
// GenerateEmbeddedObjectMeta specifies if any embedded ObjectMeta should be generated
GenerateEmbeddedObjectMeta bool
}
@@ -178,7 +181,7 @@ func (p *Parser) NeedSchemaFor(typ TypeIdent) {
// avoid tripping recursive schemata, like ManagedFields, by adding an empty WIP schema
p.Schemata[typ] = apiext.JSONSchemaProps{}
schemaCtx := newSchemaContext(typ.Package, p, p.AllowDangerousTypes)
schemaCtx := newSchemaContext(typ.Package, p, p.AllowDangerousTypes, p.IgnoreUnexportedFields)
ctxForInfo := schemaCtx.ForInfo(info)
pkgMarkers, err := markers.PackageMarkers(p.Collector, typ.Package)

View File

@@ -17,8 +17,10 @@ limitations under the License.
package crd
import (
"errors"
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
@@ -37,12 +39,10 @@ const (
defPrefix = "#/definitions/"
)
var (
// byteType is the types.Type for byte (see the types documention
// for why we need to look this up in the Universe), saved
// for quick comparison.
byteType = types.Universe.Lookup("byte").Type()
)
// byteType is the types.Type for byte (see the types documention
// for why we need to look this up in the Universe), saved
// for quick comparison.
var byteType = types.Universe.Lookup("byte").Type()
// SchemaMarker is any marker that needs to modify the schema of the underlying type or field.
type SchemaMarker interface {
@@ -69,17 +69,19 @@ type schemaContext struct {
schemaRequester schemaRequester
PackageMarkers markers.MarkerValues
allowDangerousTypes bool
allowDangerousTypes bool
ignoreUnexportedFields bool
}
// newSchemaContext constructs a new schemaContext for the given package and schema requester.
// It must have type info added before use via ForInfo.
func newSchemaContext(pkg *loader.Package, req schemaRequester, allowDangerousTypes bool) *schemaContext {
func newSchemaContext(pkg *loader.Package, req schemaRequester, allowDangerousTypes, ignoreUnexportedFields bool) *schemaContext {
pkg.NeedTypesInfo()
return &schemaContext{
pkg: pkg,
schemaRequester: req,
allowDangerousTypes: allowDangerousTypes,
pkg: pkg,
schemaRequester: req,
allowDangerousTypes: allowDangerousTypes,
ignoreUnexportedFields: ignoreUnexportedFields,
}
}
@@ -87,10 +89,11 @@ func newSchemaContext(pkg *loader.Package, req schemaRequester, allowDangerousTy
// as this one, except with the given type information.
func (c *schemaContext) ForInfo(info *markers.TypeInfo) *schemaContext {
return &schemaContext{
pkg: c.pkg,
info: info,
schemaRequester: c.schemaRequester,
allowDangerousTypes: c.allowDangerousTypes,
pkg: c.pkg,
info: info,
schemaRequester: c.schemaRequester,
allowDangerousTypes: c.allowDangerousTypes,
ignoreUnexportedFields: c.ignoreUnexportedFields,
}
}
@@ -109,6 +112,15 @@ func (c *schemaContext) requestSchema(pkgPath, typeName string) {
// infoToSchema creates a schema for the type in the given set of type information.
func infoToSchema(ctx *schemaContext) *apiext.JSONSchemaProps {
// If the obj implements a JSON marshaler and has a marker, use the markers value and do not traverse as
// the marshaler could be doing anything. If there is no marker, fall back to traversing.
if obj := ctx.pkg.Types.Scope().Lookup(ctx.info.Name); obj != nil && implementsJSONMarshaler(obj.Type()) {
schema := &apiext.JSONSchemaProps{}
applyMarkers(ctx, ctx.info.Markers, schema, ctx.info.RawSpec.Type)
if schema.Type != "" {
return schema
}
}
return typeToSchema(ctx, ctx.info.RawSpec.Type)
}
@@ -298,14 +310,12 @@ func mapToSchema(ctx *schemaContext, mapType *ast.MapType) *apiext.JSONSchemaPro
valSchema = namedToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
case *ast.ArrayType:
valSchema = arrayToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
if valSchema.Type == "array" && valSchema.Items.Schema.Type != "string" {
ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("map values must be a named type, not %T", mapType.Value), mapType.Value))
return &apiext.JSONSchemaProps{}
}
case *ast.StarExpr:
valSchema = typeToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
case *ast.MapType:
valSchema = typeToSchema(ctx.ForInfo(&markers.TypeInfo{}), val)
default:
ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("map values must be a named type, not %T", mapType.Value), mapType.Value))
ctx.pkg.AddError(loader.ErrFromNode(fmt.Errorf("not a supported map value type: %T", mapType.Value), mapType.Value))
return &apiext.JSONSchemaProps{}
}
@@ -332,6 +342,11 @@ func structToSchema(ctx *schemaContext, structType *ast.StructType) *apiext.JSON
}
for _, field := range ctx.info.Fields {
// Skip if the field is not an inline field, ignoreUnexportedFields is true, and the field is not exported
if field.Name != "" && ctx.ignoreUnexportedFields && !ast.IsExported(field.Name) {
continue
}
jsonTag, hasTag := field.Tag.Lookup("json")
if !hasTag {
// if the field doesn't have a JSON tag, it doesn't belong in output (and shouldn't exist in a serialized type)
@@ -415,10 +430,13 @@ func builtinToType(basic *types.Basic, allowDangerousTypes bool) (typ string, fo
typ = "string"
case basicInfo&types.IsInteger != 0:
typ = "integer"
case basicInfo&types.IsFloat != 0 && allowDangerousTypes:
typ = "number"
case basicInfo&types.IsFloat != 0:
if allowDangerousTypes {
typ = "number"
} else {
return "", "", errors.New("found float, the usage of which is highly discouraged, as support for them varies across languages. Please consider serializing your float as string instead. If you are really sure you want to use them, re-run with crd:allowDangerousTypes=true")
}
default:
// NB(directxman12): floats are *NOT* allowed in kubernetes APIs
return "", "", fmt.Errorf("unsupported type %q", basic.String())
}
@@ -431,3 +449,16 @@ func builtinToType(basic *types.Basic, allowDangerousTypes bool) (typ string, fo
return typ, format, nil
}
// Open coded go/types representation of encoding/json.Marshaller
var jsonMarshaler = types.NewInterfaceType([]*types.Func{
types.NewFunc(token.NoPos, nil, "MarshalJSON",
types.NewSignature(nil, nil,
types.NewTuple(
types.NewVar(token.NoPos, nil, "", types.NewSlice(types.Universe.Lookup("byte").Type())),
types.NewVar(token.NoPos, nil, "", types.Universe.Lookup("error").Type())), false)),
}, nil).Complete()
func implementsJSONMarshaler(typ types.Type) bool {
return types.Implements(typ, jsonMarshaler) || types.Implements(types.NewPointer(typ), jsonMarshaler)
}

View File

@@ -5,7 +5,7 @@ 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
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,
@@ -30,13 +30,21 @@ import (
)
// SpecMarker is a marker that knows how to apply itself to a particular
// version in a CRD.
// version in a CRD Spec.
type SpecMarker interface {
// ApplyToCRD applies this marker to the given CRD, in the given version
// within that CRD. It's called after everything else in the CRD is populated.
ApplyToCRD(crd *apiext.CustomResourceDefinitionSpec, version string) error
}
// Marker is a marker that knows how to apply itself to a particular
// version in a CRD.
type Marker interface {
// ApplyToCRD applies this marker to the given CRD, in the given version
// within that CRD. It's called after everything else in the CRD is populated.
ApplyToCRD(crd *apiext.CustomResourceDefinition, version string) error
}
// NeedCRDFor requests the full CRD for the given group-kind. It requires
// that the packages containing the Go structs for that CRD have already
// been loaded with NeedPackage.
@@ -109,12 +117,14 @@ func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) {
for _, markerVals := range typeInfo.Markers {
for _, val := range markerVals {
crdMarker, isCrdMarker := val.(SpecMarker)
if !isCrdMarker {
continue
}
if err := crdMarker.ApplyToCRD(&crd.Spec, ver); err != nil {
pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
if specMarker, isSpecMarker := val.(SpecMarker); isSpecMarker {
if err := specMarker.ApplyToCRD(&crd.Spec, ver); err != nil {
pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
}
} else if crdMarker, isCRDMarker := val.(Marker); isCRDMarker {
if err := crdMarker.ApplyToCRD(&crd, ver); err != nil {
pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
}
}
}
}
@@ -164,11 +174,5 @@ func (p *Parser) NeedCRDFor(groupKind schema.GroupKind, maxDescLen *int) {
packages[0].AddError(fmt.Errorf("CRD for %s with version(s) %v does not serve any version", groupKind, crd.Spec.Versions))
}
// NB(directxman12): CRD's status doesn't have omitempty markers, which means things
// get serialized as null, which causes the validator to freak out. Manually set
// these to empty till we get a better solution.
crd.Status.Conditions = []apiext.CustomResourceDefinitionCondition{}
crd.Status.StoredVersions = []string{}
p.CustomResourceDefinitions[groupKind] = crd
}

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
@@ -32,13 +33,9 @@ func (Generator) Help() *markers.DefinitionHelp {
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{
"TrivialVersions": {
Summary: "indicates that we should produce a single-version CRD. ",
Details: "Single \"trivial-version\" CRDs are compatible with older (pre 1.13) Kubernetes API servers. The storage version's schema will be used as the CRD's schema. \n Only works with the v1beta1 CRD version.",
},
"PreserveUnknownFields": {
Summary: "indicates whether or not we should turn off pruning. ",
Details: "Left unspecified, it'll default to true when only a v1beta1 CRD is generated (to preserve compatibility with older versions of this tool), or false otherwise. \n It's required to be false for v1 CRDs.",
"IgnoreUnexportedFields": {
Summary: "indicates that we should skip unexported fields. ",
Details: "Left unspecified, the default is false.",
},
"AllowDangerousTypes": {
Summary: "allows types which are usually omitted from CRD generation because they are not recommended. ",
@@ -50,7 +47,7 @@ func (Generator) Help() *markers.DefinitionHelp {
},
"CRDVersions": {
Summary: "specifies the target API versions of the CRD type itself to generate. Defaults to v1. ",
Details: "The first version listed will be assumed to be the \"default\" version and will not get a version suffix in the output filename. \n You'll need to use \"v1\" to get support for features like defaulting, along with an API server that supports it (Kubernetes 1.16+).",
Details: "Currently, the only supported value is v1. \n The first version listed will be assumed to be the \"default\" version and will not get a version suffix in the output filename. \n You'll need to use \"v1\" to get support for features like defaulting, along with an API server that supports it (Kubernetes 1.16+).",
},
"GenerateEmbeddedObjectMeta": {
Summary: "specifies if any embedded ObjectMeta in the CRD should be generated",

View File

@@ -175,7 +175,8 @@ type ObjectGenCtx struct {
// writeHeader writes out the build tag, package declaration, and imports
func writeHeader(pkg *loader.Package, out io.Writer, packageName string, imports *importsList, headerText string) {
// NB(directxman12): blank line after build tags to distinguish them from comments
_, err := fmt.Fprintf(out, `// +build !ignore_autogenerated
_, err := fmt.Fprintf(out, `//go:build !ignore_autogenerated
// +build !ignore_autogenerated
%[3]s

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -17,14 +17,14 @@ limitations under the License.
// Package genall defines entrypoints for generation tools to hook into and
// share the same set of parsing, typechecking, and marker information.
//
// Generators
// # Generators
//
// Each Generator knows how to register its markers into a central Registry,
// and then how to generate output using a Collector and some root packages.
// Each generator can be considered to be the output type of a marker, for easy
// command line parsing.
//
// Output and Input
// # Output and Input
//
// Generators output artifacts via an OutputRule. OutputRules know how to
// write output for different package-associated (code) files, as well as
@@ -40,7 +40,7 @@ limitations under the License.
// InputRule defines custom input loading, but its shared across all
// Generators. There's currently only a filesystem implementation.
//
// Runtime and Context
// # Runtime and Context
//
// Runtime maps together Generators, and constructs "contexts" which provide
// the common collector and roots, plus the output rule for that generator, and
@@ -50,7 +50,7 @@ limitations under the License.
// skipping type-checking errors (since those are commonly caused by the
// partial type-checking of loader.TypeChecker).
//
// Options
// # Options
//
// The FromOptions (and associated helpers) function makes it easy to use generators
// and output rules as markers that can be parsed from the command line, producing

View File

@@ -17,13 +17,14 @@ limitations under the License.
package genall
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"golang.org/x/tools/go/packages"
"sigs.k8s.io/yaml"
rawyaml "gopkg.in/yaml.v2"
"sigs.k8s.io/controller-tools/pkg/loader"
"sigs.k8s.io/controller-tools/pkg/markers"
@@ -100,6 +101,8 @@ type Runtime struct {
GenerationContext
// OutputRules defines how to output artifacts for each Generator.
OutputRules OutputRules
// ErrorWriter defines where to write error messages.
ErrorWriter io.Writer
}
// GenerationContext defines the common information needed for each Generator
@@ -118,10 +121,22 @@ type GenerationContext struct {
InputRule
}
// WriteYAMLOptions implements the Options Pattern for WriteYAML.
type WriteYAMLOptions struct {
transform func(obj map[string]interface{}) error
}
// WithTransform applies a transformation to objects just before writing them.
func WithTransform(transform func(obj map[string]interface{}) error) *WriteYAMLOptions {
return &WriteYAMLOptions{
transform: transform,
}
}
// WriteYAML writes the given objects out, serialized as YAML, using the
// context's OutputRule. Objects are written as separate documents, separated
// from each other by `---` (as per the YAML spec).
func (g GenerationContext) WriteYAML(itemPath string, objs ...interface{}) error {
func (g GenerationContext) WriteYAML(itemPath string, objs []interface{}, options ...*WriteYAMLOptions) error {
out, err := g.Open(nil, itemPath)
if err != nil {
return err
@@ -129,11 +144,11 @@ func (g GenerationContext) WriteYAML(itemPath string, objs ...interface{}) error
defer out.Close()
for _, obj := range objs {
yamlContent, err := yaml.Marshal(obj)
yamlContent, err := yamlMarshal(obj, options...)
if err != nil {
return err
}
n, err := out.Write(append([]byte("\n---\n"), yamlContent...))
n, err := out.Write(append([]byte("---\n"), yamlContent...))
if err != nil {
return err
}
@@ -145,6 +160,41 @@ func (g GenerationContext) WriteYAML(itemPath string, objs ...interface{}) error
return nil
}
// yamlMarshal is based on sigs.k8s.io/yaml.Marshal, but allows for transforming the final data before writing.
func yamlMarshal(o interface{}, options ...*WriteYAMLOptions) ([]byte, error) {
j, err := json.Marshal(o)
if err != nil {
return nil, fmt.Errorf("error marshaling into JSON: %v", err)
}
return yamlJSONToYAMLWithFilter(j, options...)
}
// yamlJSONToYAMLWithFilter is based on sigs.k8s.io/yaml.JSONToYAML, but allows for transforming the final data before writing.
func yamlJSONToYAMLWithFilter(j []byte, options ...*WriteYAMLOptions) ([]byte, error) {
// Convert the JSON to an object.
var jsonObj map[string]interface{}
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
// Go JSON library doesn't try to pick the right number type (int, float,
// etc.) when unmarshalling to interface{}, it just picks float64
// universally. go-yaml does go through the effort of picking the right
// number type, so we can preserve number type throughout this process.
if err := rawyaml.Unmarshal(j, &jsonObj); err != nil {
return nil, err
}
for _, option := range options {
if option.transform != nil {
if err := option.transform(jsonObj); err != nil {
return nil, err
}
}
}
// Marshal this object into YAML.
return rawyaml.Marshal(jsonObj)
}
// ReadFile reads the given boilerplate artifact using the context's InputRule.
func (g GenerationContext) ReadFile(path string) ([]byte, error) {
file, err := g.OpenForRead(path)
@@ -188,8 +238,12 @@ func (g Generators) ForRoots(rootPaths ...string) (*Runtime, error) {
func (r *Runtime) Run() bool {
// TODO(directxman12): we could make this parallel,
// but we'd need to ensure all underlying machinery is threadsafe
if r.ErrorWriter == nil {
r.ErrorWriter = os.Stderr
}
if len(r.Generators) == 0 {
fmt.Fprintln(os.Stderr, "no generators to run")
fmt.Fprintln(r.ErrorWriter, "no generators to run")
return true
}
@@ -205,7 +259,7 @@ func (r *Runtime) Run() bool {
}
if err := (*gen).Generate(&ctx); err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(r.ErrorWriter, err)
hadErrs = true
}
}

View File

@@ -17,13 +17,13 @@ limitations under the License.
// Package pretty contains utilities for formatting terminal help output,
// and a use of those to display marker help.
//
// Terminal Output
// # Terminal Output
//
// The Span interface and Table struct allow you to construct tables with
// colored formatting, without causing ANSI formatting characters to mess up width
// calculations.
//
// Marker Help
// # Marker Help
//
// The MarkersSummary prints a summary table for marker help, while the MarkersDetails
// prints out more detailed information, with explainations of the individual marker fields.

View File

@@ -30,6 +30,8 @@ var (
// +controllertools:marker:generateHelp:category=""
// InputPaths represents paths and go-style path patterns to use as package roots.
//
// Multiple paths can be specified using "{path1, path2, path3}".
type InputPaths []string
// RegisterOptionsMarkers registers "mandatory" options markers for FromOptions into the given registry.

View File

@@ -103,7 +103,7 @@ type OutputToDirectory string
func (o OutputToDirectory) Open(_ *loader.Package, itemPath string) (io.WriteCloser, error) {
// ensure the directory exists
if err := os.MkdirAll(string(o), os.ModePerm); err != nil {
if err := os.MkdirAll(filepath.Dir(filepath.Join(string(o), itemPath)), os.ModePerm); err != nil {
return nil, err
}
path := filepath.Join(string(o), itemPath)

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
@@ -28,8 +29,8 @@ func (InputPaths) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "",
DetailedHelp: markers.DetailedHelp{
Summary: "represents paths and go-style path patterns to use as package roots.",
Details: "",
Summary: "represents paths and go-style path patterns to use as package roots. ",
Details: "Multiple paths can be specified using \"{path1, path2, path3}\".",
},
FieldHelp: map[string]markers.DetailedHelp{},
}

View File

@@ -22,7 +22,7 @@ limitations under the License.
// Because it uses go/packages, it's modules-aware, and works in both modules-
// and non-modules environments.
//
// Loading
// # Loading
//
// The main entrypoint for loading is LoadRoots, which traverse the package
// graph starting at the given patterns (file, package, path, or ...-wildcard,
@@ -33,7 +33,7 @@ limitations under the License.
// Packages are suitable for comparison, as each unique package only ever has
// one *Package object returned.
//
// Syntax and TypeChecking
// # Syntax and TypeChecking
//
// ASTs and type-checking information can be loaded with NeedSyntax and
// NeedTypesInfo, respectively. Both are idempotent -- repeated calls will
@@ -41,14 +41,14 @@ limitations under the License.
// check the current package -- if you want to type-check imports as well,
// you'll need to type-check them first.
//
// Reference Pruning and Recursive Checking
// # Reference Pruning and Recursive Checking
//
// In order to type-check using only the packages you care about, you can use a
// TypeChecker. TypeChecker will visit each top-level type declaration,
// collect (optionally filtered) references, and type-check references
// packages.
//
// Errors
// # Errors
//
// Errors can be added to each package. Use ErrFromNode to create an error
// from an AST node. Errors can then be printed (complete with file and

View File

@@ -1,5 +1,5 @@
/*
Copyright 2019 The Kubernetes Authors.
Copyright 2019-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.
@@ -25,9 +25,12 @@ import (
"go/types"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"sync"
"golang.org/x/tools/go/packages"
"k8s.io/apimachinery/pkg/util/sets"
)
// Much of this is strongly inspired by the contents of go/packages,
@@ -329,6 +332,40 @@ func LoadRoots(roots ...string) ([]*Package, error) {
//
// This is generally only useful for use in testing when you need to modify
// loading settings to load from a fake location.
//
// This function will traverse Go module boundaries for roots that are file-
// system paths and end with "...". Please note this feature currently only
// supports roots that are filesystem paths. For more information, please
// refer to the high-level outline of this function's logic:
//
// 1. If no roots are provided then load the working directory and return
// early.
//
// 2. Otherwise sort the provided roots into two, distinct buckets:
//
// a. package/module names
// b. filesystem paths
//
// A filesystem path is distinguished from a Go package/module name by
// the same rules as followed by the "go" command. At a high level, a
// root is a filesystem path IFF it meets ANY of the following criteria:
//
// * is absolute
// * begins with .
// * begins with ..
//
// For more information please refer to the output of the command
// "go help packages".
//
// 3. Load the package/module roots as a single call to packages.Load. If
// there are no filesystem path roots then return early.
//
// 4. For filesystem path roots ending with "...", check to see if its
// descendants include any nested, Go modules. If so, add the directory
// that contains the nested Go module to the filesystem path roots.
//
// 5. Load the filesystem path roots and return the load packages for the
// package/module roots AND the filesystem path roots.
func LoadRootsWithConfig(cfg *packages.Config, roots ...string) ([]*Package, error) {
l := &loader{
cfg: cfg,
@@ -341,18 +378,250 @@ func LoadRootsWithConfig(cfg *packages.Config, roots ...string) ([]*Package, err
// put our build flags first so that callers can override them
l.cfg.BuildFlags = append([]string{"-tags", "ignore_autogenerated"}, l.cfg.BuildFlags...)
rawPkgs, err := packages.Load(l.cfg, roots...)
if err != nil {
return nil, err
// Visit the import graphs of the loaded, root packages. If an imported
// package refers to another loaded, root package, then replace the
// instance of the imported package with a reference to the loaded, root
// package. This is required to make kubebuilder markers work correctly
// when multiple root paths are loaded and types from one path reference
// types from another root path.
defer func() {
for i := range l.Roots {
visitImports(l.Roots, l.Roots[i], nil)
}
}()
// uniquePkgIDs is used to keep track of the discovered packages to be nice
// and try and prevent packages from showing up twice when nested module
// support is enabled. there is not harm that comes from this per se, but
// it makes testing easier when a known number of modules can be asserted
uniquePkgIDs := sets.String{}
// loadPackages returns the Go packages for the provided roots
//
// if validatePkgFn is nil, a package will be returned in the slice,
// otherwise the package is only returned if the result of
// validatePkgFn(pkg.ID) is truthy
loadPackages := func(roots ...string) ([]*Package, error) {
rawPkgs, err := packages.Load(l.cfg, roots...)
if err != nil {
loadRoot := l.cfg.Dir
if l.cfg.Dir == "" {
loadRoot, _ = os.Getwd()
}
return nil, fmt.Errorf("load packages in root %q: %w", loadRoot, err)
}
var pkgs []*Package
for _, rp := range rawPkgs {
p := l.packageFor(rp)
if !uniquePkgIDs.Has(p.ID) {
pkgs = append(pkgs, p)
uniquePkgIDs.Insert(p.ID)
}
}
return pkgs, nil
}
for _, rawPkg := range rawPkgs {
l.Roots = append(l.Roots, l.packageFor(rawPkg))
// if no roots were provided then load the current package and return early
if len(roots) == 0 {
pkgs, err := loadPackages()
if err != nil {
return nil, err
}
l.Roots = append(l.Roots, pkgs...)
return l.Roots, nil
}
// pkgRoots is a slice of roots that are package/modules and fspRoots
// is a slice of roots that are local filesystem paths.
//
// please refer to this function's godoc comments for more information on
// how these two types of roots are distinguished from one another
var (
pkgRoots []string
fspRoots []string
fspRootRx = regexp.MustCompile(`^\.{1,2}`)
)
for _, r := range roots {
if filepath.IsAbs(r) || fspRootRx.MatchString(r) {
fspRoots = append(fspRoots, r)
} else {
pkgRoots = append(pkgRoots, r)
}
}
// handle the package roots by sending them into the packages.Load function
// all at once. this is more efficient, but cannot be used for the file-
// system path roots due to them needing a custom, calculated value for the
// cfg.Dir field
if len(pkgRoots) > 0 {
pkgs, err := loadPackages(pkgRoots...)
if err != nil {
return nil, err
}
l.Roots = append(l.Roots, pkgs...)
}
// if there are no filesystem path roots then go ahead and return early
if len(fspRoots) == 0 {
return l.Roots, nil
}
//
// at this point we are handling filesystem path roots
//
// ensure the cfg.Dir field is reset to its original value upon
// returning from this function. it should honestly be fine if it is
// not given most callers will not send in the cfg parameter directly,
// as it's largely for testing, but still, let's be good stewards.
defer func(d string) {
cfg.Dir = d
}(cfg.Dir)
// store the value of cfg.Dir so we can use it later if it is non-empty.
// we need to store it now as the value of cfg.Dir will be updated by
// a loop below
cfgDir := cfg.Dir
// addNestedGoModulesToRoots is given to filepath.WalkDir and adds the
// directory part of p to the list of filesystem path roots IFF p is the
// path to a file named "go.mod"
addNestedGoModulesToRoots := func(
p string,
d os.DirEntry,
e error) error {
if e != nil {
return e
}
if !d.IsDir() && filepath.Base(p) == "go.mod" {
fspRoots = append(fspRoots, filepath.Join(filepath.Dir(p), "..."))
}
return nil
}
// in the first pass over the filesystem path roots we:
//
// 1. make the root into an absolute path
//
// 2. check to see if a root uses the nested path syntax, ex. ...
//
// 3. if so, walk the root's descendants, searching for any nested Go
// modules
//
// 4. if found then the directory containing the Go module is added to
// the list of the filesystem path roots
for i := range fspRoots {
r := fspRoots[i]
// clean up the root
r = filepath.Clean(r)
// get the absolute path of the root
if !filepath.IsAbs(r) {
// if the initial value of cfg.Dir was non-empty then use it when
// building the absolute path to this root. otherwise use the
// filepath.Abs function to get the absolute path of the root based
// on the working directory
if cfgDir != "" {
r = filepath.Join(cfgDir, r)
} else {
ar, err := filepath.Abs(r)
if err != nil {
return nil, err
}
r = ar
}
}
// update the root to be an absolute path
fspRoots[i] = r
b, d := filepath.Base(r), filepath.Dir(r)
// if the base element is "..." then it means nested traversal is
// activated. this can be passed directly to the loader. however, if
// specified we also want to traverse the path manually to determine if
// there are any nested Go modules we want to add to the list of file-
// system path roots to process
if b == "..." {
if err := filepath.WalkDir(
d,
addNestedGoModulesToRoots); err != nil {
return nil, err
}
}
}
// in the second pass over the filesystem path roots we:
//
// 1. determine the directory from which to execute the loader
//
// 2. update the loader config's Dir property to be the directory from
// step one
//
// 3. determine whether the root passed to the loader should be "./."
// or "./..."
//
// 4. execute the loader with the value from step three
for _, r := range fspRoots {
b, d := filepath.Base(r), filepath.Dir(r)
// we want the base part of the path to be either "..." or ".", except
// Go's filepath utilities clean paths during manipulation, removing the
// ".". thus, if not "...", let's update the path components so that:
//
// d = r
// b = "."
if b != "..." {
d = r
b = "."
}
// update the loader configuration's Dir field to the directory part of
// the root
l.cfg.Dir = d
// update the root to be "./..." or "./."
// (with OS-specific filepath separator). please note filepath.Join
// would clean up the trailing "." character that we want preserved,
// hence the more manual path concatenation logic
r = fmt.Sprintf(".%s%s", string(filepath.Separator), b)
// load the packages from the roots
pkgs, err := loadPackages(r)
if err != nil {
return nil, err
}
l.Roots = append(l.Roots, pkgs...)
}
return l.Roots, nil
}
// visitImports walks a dependency graph, replacing imported package
// references with those from the rootPkgs list. This ensures the
// kubebuilder marker generation is handled correctly. For more info,
// please see issue 680.
func visitImports(rootPkgs []*Package, pkg *Package, seen sets.String) {
if seen == nil {
seen = sets.String{}
}
for importedPkgID, importedPkg := range pkg.Imports() {
for i := range rootPkgs {
if importedPkgID == rootPkgs[i].ID {
pkg.imports[importedPkgID] = rootPkgs[i]
}
}
if !seen.Has(importedPkgID) {
seen.Insert(importedPkgID)
visitImports(rootPkgs, importedPkg, seen)
}
}
}
// importFunc is an implementation of the single-method
// types.Importer interface based on a function value.
type importerFunc func(path string) (*types.Package, error)

View File

@@ -133,9 +133,14 @@ func (c *referenceCollector) Visit(node ast.Node) ast.Visitor {
// local reference or dot-import, ignore
return nil
case *ast.SelectorExpr:
pkgName := typedNode.X.(*ast.Ident).Name
c.refs.external(pkgName)
return nil
switch x := typedNode.X.(type) {
case *ast.Ident:
pkgName := x.Name
c.refs.external(pkgName)
return nil
default:
return c
}
default:
return c
}

View File

@@ -65,13 +65,13 @@ func (c *Collector) init() {
//
// - it's in the Godoc for that AST node
//
// - it's in the closest non-godoc comment group above that node,
// *and* that node is a type or field node, *and* [it's either
// registered as type-level *or* it's not registered as being
// package-level]
// - it's in the closest non-godoc comment group above that node,
// *and* that node is a type or field node, *and* [it's either
// registered as type-level *or* it's not registered as being
// package-level]
//
// - it's not in the Godoc of a node, doesn't meet the above criteria, and
// isn't in a struct definition (in which case it's package-level)
// - it's not in the Godoc of a node, doesn't meet the above criteria, and
// isn't in a struct definition (in which case it's package-level)
func (c *Collector) MarkersInPackage(pkg *loader.Package) (map[ast.Node]MarkerValues, error) {
c.mu.Lock()
c.init()

View File

@@ -19,7 +19,7 @@ limitations under the License.
// avoid confusing with struct tags). Parsed result (output) values take the
// form of Go values, much like the "encoding/json" package.
//
// Definitions and Parsing
// # Definitions and Parsing
//
// Markers are defined as structured Definitions which can be used to
// consistently parse marker comments. A Definition contains an concrete
@@ -29,20 +29,20 @@ limitations under the License.
//
// Markers take the general form
//
// +path:to:marker=val
// +path:to:marker=val
//
// +path:to:marker:arg1=val,arg2=val2
// +path:to:marker:arg1=val,arg2=val2
//
// +path:to:marker
// +path:to:marker
//
// Arguments may be ints, bools, strings, and slices. Ints and bool take their
// standard form from Go. Strings may take any of their standard forms, or any
// sequence of unquoted characters up until a `,` or `;` is encountered. Lists
// take either of the following forms:
//
// val;val;val
// val;val;val
//
// {val, val, val}
// {val, val, val}
//
// Note that the first form will not properly parse nested slices, but is
// generally convenient and is the form used in many existing markers.
@@ -61,7 +61,7 @@ limitations under the License.
// non-optional fields aren't mentioned, an error will be raised unless
// `Strict` is set to false.
//
// Registries and Lookup
// # Registries and Lookup
//
// Definitions can be added to registries to facilitate lookups. Each
// definition is marked as either describing a type, struct field, or package
@@ -69,7 +69,7 @@ limitations under the License.
// long as each describes a different construct (type, field, or package).
// Definitions can then be looked up by passing unparsed markers.
//
// Collection and Extraction
// # Collection and Extraction
//
// Markers can be collected from a loader.Package using a Collector. The
// Collector will read from a given Registry, collecting comments that look
@@ -85,7 +85,7 @@ limitations under the License.
// Like loader.Package, Collector's methods are idempotent and will not
// reperform work.
//
// Traversal
// # Traversal
//
// EachType function iterates over each type in a Package, providing
// conveniently structured type and field information with marker values
@@ -93,14 +93,14 @@ limitations under the License.
//
// PackageMarkers can be used to fetch just package-level markers.
//
// Help
// # Help
//
// Help can be defined for each marker using the DefinitionHelp struct. It's
// mostly intended to be generated off of godocs using cmd/helpgen, which takes
// the first line as summary (removing the type/field name), and considers the
// rest as details. It looks for the
//
// +controllertools:generateHelp[:category=<string>]
// +controllertools:generateHelp[:category=<string>]
//
// marker to start generation.
//

View File

@@ -84,6 +84,8 @@ const (
InvalidType ArgumentType = iota
// IntType is an int
IntType
// NumberType is a float64
NumberType
// StringType is a string
StringType
// BoolType is a bool
@@ -127,6 +129,8 @@ func (a Argument) typeString(out *strings.Builder) {
out.WriteString("<invalid>")
case IntType:
out.WriteString("int")
case NumberType:
out.WriteString("float64")
case StringType:
out.WriteString("string")
case BoolType:
@@ -180,6 +184,8 @@ func makeSliceType(itemType Argument) (reflect.Type, error) {
switch itemType.Type {
case IntType:
itemReflectedType = reflect.TypeOf(int(0))
case NumberType:
itemReflectedType = reflect.TypeOf(float64(0))
case StringType:
itemReflectedType = reflect.TypeOf("")
case BoolType:
@@ -215,6 +221,8 @@ func makeMapType(itemType Argument) (reflect.Type, error) {
switch itemType.Type {
case IntType:
itemReflectedType = reflect.TypeOf(int(0))
case NumberType:
itemReflectedType = reflect.TypeOf(float64(0))
case StringType:
itemReflectedType = reflect.TypeOf("")
case BoolType:
@@ -346,9 +354,13 @@ func guessType(scanner *sc.Scanner, raw string, allowSlice bool) *Argument {
if nextTok == '-' {
nextTok = subScanner.Scan()
}
if nextTok == sc.Int {
return &Argument{Type: IntType}
}
if nextTok == sc.Float {
return &Argument{Type: NumberType}
}
}
// otherwise assume bare strings
@@ -471,7 +483,7 @@ func (a *Argument) parseMap(scanner *sc.Scanner, raw string, out reflect.Value)
func (a *Argument) parse(scanner *sc.Scanner, raw string, out reflect.Value, inSlice bool) {
// nolint:gocyclo
if a.Type == InvalidType {
scanner.Error(scanner, fmt.Sprintf("cannot parse invalid type"))
scanner.Error(scanner, "cannot parse invalid type")
return
}
if a.Pointer {
@@ -485,6 +497,32 @@ func (a *Argument) parse(scanner *sc.Scanner, raw string, out reflect.Value, inS
// consume everything else
for tok := scanner.Scan(); tok != sc.EOF; tok = scanner.Scan() {
}
case NumberType:
nextChar := scanner.Peek()
isNegative := false
if nextChar == '-' {
isNegative = true
scanner.Scan() // eat the '-'
}
tok := scanner.Scan()
if tok != sc.Float && tok != sc.Int {
scanner.Error(scanner, fmt.Sprintf("expected integer or float, got %q", scanner.TokenText()))
return
}
text := scanner.TokenText()
if isNegative {
text = "-" + text
}
val, err := strconv.ParseFloat(text, 64)
if err != nil {
scanner.Error(scanner, fmt.Sprintf("unable to parse number: %v", err))
return
}
castAndSet(out, reflect.ValueOf(val))
case IntType:
nextChar := scanner.Peek()
isNegative := false
@@ -597,6 +635,8 @@ func ArgumentFromType(rawType reflect.Type) (Argument, error) {
arg.Type = StringType
case reflect.Int, reflect.Int32: // NB(directxman12): all ints in kubernetes are int32, so explicitly support that
arg.Type = IntType
case reflect.Float64:
arg.Type = NumberType
case reflect.Bool:
arg.Type = BoolType
case reflect.Slice:
@@ -755,7 +795,7 @@ func (d *Definition) loadFields() error {
func parserScanner(raw string, err func(*sc.Scanner, string)) *sc.Scanner {
scanner := &sc.Scanner{}
scanner.Init(bytes.NewBufferString(raw))
scanner.Mode = sc.ScanIdents | sc.ScanInts | sc.ScanStrings | sc.ScanRawStrings | sc.SkipComments
scanner.Mode = sc.ScanIdents | sc.ScanInts | sc.ScanFloats | sc.ScanStrings | sc.ScanRawStrings | sc.SkipComments
scanner.Error = err
return scanner

View File

@@ -52,7 +52,8 @@ func (r *Registry) init() {
// Define defines a new marker with the given name, target, and output type.
// It's a shortcut around
// r.Register(MakeDefinition(name, target, obj))
//
// r.Register(MakeDefinition(name, target, obj))
func (r *Registry) Define(name string, target TargetType, obj interface{}) error {
def, err := MakeDefinition(name, target, obj)
if err != nil {

View File

@@ -67,12 +67,22 @@ func extractDoc(node ast.Node, decl *ast.GenDecl) string {
// chop off the extraneous last part
outLines = outLines[:len(outLines)-1]
}
// respect double-newline meaning actual newline
for i, line := range outLines {
// Trim any extranous whitespace,
// for handling /*…*/-style comments,
// which have whitespace preserved in go/ast:
line = strings.TrimSpace(line)
// Respect that double-newline means
// actual newline:
if line == "" {
outLines[i] = "\n"
} else {
outLines[i] = line
}
}
return strings.Join(outLines, " ")
}
@@ -139,11 +149,11 @@ type TypeCallback func(info *TypeInfo)
// EachType collects all markers, then calls the given callback for each type declaration in a package.
// Each individual spec is considered separate, so
//
// type (
// Foo string
// Bar int
// Baz struct{}
// )
// type (
// Foo string
// Bar int
// Baz struct{}
// )
//
// yields three calls to the callback.
func EachType(col *Collector, pkg *loader.Package, cb TypeCallback) error {

View File

@@ -19,7 +19,7 @@ limitations under the License.
//
// The markers take the form:
//
// +kubebuilder:rbac:groups=<groups>,resources=<resources>,resourceNames=<resource names>,verbs=<verbs>,urls=<non resource urls>
// +kubebuilder:rbac:groups=<groups>,resources=<resources>,resourceNames=<resource names>,verbs=<verbs>,urls=<non resource urls>
package rbac
import (
@@ -263,5 +263,5 @@ func (g Generator) Generate(ctx *genall.GenerationContext) error {
return nil
}
return ctx.WriteYAML("role.yaml", objs...)
return ctx.WriteYAML("role.yaml", objs)
}

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*

View File

@@ -23,7 +23,6 @@ import (
"gopkg.in/yaml.v3"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextlegacy "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -54,7 +53,6 @@ import (
// patches.
var (
legacyAPIExtVersion = apiextlegacy.SchemeGroupVersion.String()
currentAPIExtVersion = apiext.SchemeGroupVersion.String()
)
@@ -62,18 +60,8 @@ var (
// Generator patches existing CRDs with new schemata.
//
// For legacy (v1beta1) single-version CRDs, it will simply replace the global schema.
//
// For legacy (v1beta1) multi-version CRDs, and any v1 CRDs, it will replace
// schemata of existing versions and *clear the schema* from any versions not
// specified in the Go code. It will *not* add new versions, or remove old
// ones.
//
// For legacy multi-version CRDs with identical schemata, it will take care of
// lifting the per-version schema up to the global schema.
//
// It will generate output for each "CRD Version" (API version of the CRD type
// itself) , e.g. apiextensions/v1beta1 and apiextensions/v1) available.
// itself) , e.g. apiextensions/v1) available.
type Generator struct {
// ManifestsPath contains the CustomResourceDefinition YAML files.
ManifestsPath string `marker:"manifests"`
@@ -125,7 +113,7 @@ func (g Generator) Generate(ctx *genall.GenerationContext) (result error) {
}
// generate schemata for the types we care about, and save them to be written later.
for groupKind := range crdgen.FindKubeKinds(parser, metav1Pkg) {
for _, groupKind := range crdgen.FindKubeKinds(parser, metav1Pkg) {
existingSet, wanted := partialCRDSets[groupKind]
if !wanted {
continue
@@ -259,70 +247,16 @@ type partialCRD struct {
CRDVersion string
}
// setGlobalSchema sets the global schema for the v1beta1 apiext version in
// this set (if present, as per partialCRD.setGlobalSchema), and sets the
// versioned schemas (as per setVersionedSchemata) for the v1 version.
// setGlobalSchema sets the versioned schemas (as per setVersionedSchemata).
func (e *partialCRDSet) setGlobalSchema() error {
// there's no easy way to get a "random" key from a go map :-/
var schema apiext.JSONSchemaProps
for ver := range e.NewSchemata {
schema = e.NewSchemata[ver]
break
}
for _, crdInfo := range e.CRDVersions {
switch crdInfo.CRDVersion {
case legacyAPIExtVersion:
if err := crdInfo.setGlobalSchema(schema); err != nil {
return err
}
case currentAPIExtVersion:
// just set the schemata as normal for non-legacy versions
if err := crdInfo.setVersionedSchemata(e.NewSchemata); err != nil {
return err
}
if err := crdInfo.setVersionedSchemata(e.NewSchemata); err != nil {
return err
}
}
return nil
}
// setGlobalSchema sets the global schema to one of the schemata
// for this CRD. All schemata must be identical for this to be a valid operation.
func (e *partialCRD) setGlobalSchema(newSchema apiext.JSONSchemaProps) error {
if e.CRDVersion != legacyAPIExtVersion {
// no global schema, nothing to do
return fmt.Errorf("cannot set global schema on non-legacy CRD versions")
}
schema, err := legacySchema(newSchema)
if err != nil {
return fmt.Errorf("failed to convert schema to legacy form: %w", err)
}
schemaNodeTree, err := yamlop.ToYAML(schema)
if err != nil {
return err
}
schemaNodeTree = schemaNodeTree.Content[0] // get rid of the document node
yamlop.SetStyle(schemaNodeTree, 0) // clear the style so it defaults to auto-style-choice
if err := yamlop.SetNode(e.Yaml, *schemaNodeTree, "spec", "validation", "openAPIV3Schema"); err != nil {
return err
}
versions, found, err := e.getVersionsNode()
if err != nil {
return err
}
if !found {
return nil
}
for i, verNode := range versions.Content {
if err := yamlop.DeleteNode(verNode, "schema"); err != nil {
return fmt.Errorf("spec.versions[%d]: %w", i, err)
}
}
return nil
}
// getVersionsNode gets the YAML node of .spec.versions YAML mapping,
// if returning the node, and whether or not it was present.
func (e *partialCRD) getVersionsNode() (*yaml.Node, bool, error) {
@@ -382,16 +316,7 @@ func (e *partialCRD) setVersionedSchemata(newSchemata map[string]apiext.JSONSche
return fmt.Errorf("spec.versions[%d]: %w", i, err)
}
} else {
// TODO(directxman12): if this gets to be more than 2 versions, use polymorphism to clean this up
var verSchema interface{} = newSchema
if e.CRDVersion == legacyAPIExtVersion {
verSchema, err = legacySchema(newSchema)
if err != nil {
return fmt.Errorf("failed to convert schema to legacy form: %w", err)
}
}
schemaNodeTree, err := yamlop.ToYAML(verSchema)
schemaNodeTree, err := yamlop.ToYAML(newSchema)
if err != nil {
return fmt.Errorf("failed to convert schema to YAML: %w", err)
}
@@ -433,24 +358,26 @@ func crdsFromDirectory(ctx *genall.GenerationContext, dir string) (map[schema.Gr
if err := kyaml.Unmarshal(rawContent, &typeMeta); err != nil {
continue
}
if !isSupportedAPIExtGroupVer(typeMeta.APIVersion) || typeMeta.Kind != "CustomResourceDefinition" {
if typeMeta.APIVersion == "" || typeMeta.Kind != "CustomResourceDefinition" {
// If there's no API version this file probably isn't a CRD.
// Likewise we don't need to care if the Kind isn't CustomResourceDefinition.
continue
}
if !isSupportedAPIExtGroupVer(typeMeta.APIVersion) {
return nil, fmt.Errorf("load %q: apiVersion %q not supported", filepath.Join(dir, fileInfo.Name()), typeMeta.APIVersion)
}
// collect the group-kind and versions from the actual structured form
var actualCRD crdIsh
if err := kyaml.Unmarshal(rawContent, &actualCRD); err != nil {
continue
}
groupKind := schema.GroupKind{Group: actualCRD.Spec.Group, Kind: actualCRD.Spec.Names.Kind}
var versions map[string]struct{}
if len(actualCRD.Spec.Versions) == 0 {
versions = map[string]struct{}{actualCRD.Spec.Version: {}}
} else {
versions = make(map[string]struct{}, len(actualCRD.Spec.Versions))
for _, ver := range actualCRD.Spec.Versions {
versions[ver.Name] = struct{}{}
}
versions := make(map[string]struct{}, len(actualCRD.Spec.Versions))
for _, ver := range actualCRD.Spec.Versions {
versions[ver.Name] = struct{}{}
}
// then actually unmarshal in a manner that preserves ordering, etc
@@ -480,9 +407,9 @@ func crdsFromDirectory(ctx *genall.GenerationContext, dir string) (map[schema.Gr
}
// isSupportedAPIExtGroupVer checks if the given string-form group-version
// is one of the known apiextensions versions (v1, v1beta1).
// is one of the known apiextensions versions (v1).
func isSupportedAPIExtGroupVer(groupVer string) bool {
return groupVer == currentAPIExtVersion || groupVer == legacyAPIExtVersion
return groupVer == currentAPIExtVersion
}
// crdIsh is a merged blob of CRD fields that looks enough like all versions of
@@ -502,23 +429,5 @@ type crdIsh struct {
Versions []struct {
Name string `json:"name"`
} `json:"versions"`
Version string `json:"version"`
} `json:"spec"`
}
// legacySchema jumps through some hoops to convert a v1 schema to a v1beta1 schema.
func legacySchema(origSchema apiext.JSONSchemaProps) (apiextlegacy.JSONSchemaProps, error) {
shellCRD := apiext.CustomResourceDefinition{}
shellCRD.APIVersion = currentAPIExtVersion
shellCRD.Kind = "CustomResourceDefinition"
shellCRD.Spec.Versions = []apiext.CustomResourceDefinitionVersion{
{Schema: &apiext.CustomResourceValidation{OpenAPIV3Schema: origSchema.DeepCopy()}},
}
legacyCRD, err := crdgen.AsVersion(shellCRD, apiextlegacy.SchemeGroupVersion)
if err != nil {
return apiextlegacy.JSONSchemaProps{}, err
}
return *legacyCRD.(*apiextlegacy.CustomResourceDefinition).Spec.Validation.OpenAPIV3Schema, nil
}

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
@@ -29,7 +30,7 @@ func (Generator) Help() *markers.DefinitionHelp {
Category: "",
DetailedHelp: markers.DetailedHelp{
Summary: "patches existing CRDs with new schemata. ",
Details: "For legacy (v1beta1) single-version CRDs, it will simply replace the global schema. \n For legacy (v1beta1) multi-version CRDs, and any v1 CRDs, it will replace schemata of existing versions and *clear the schema* from any versions not specified in the Go code. It will *not* add new versions, or remove old ones. \n For legacy multi-version CRDs with identical schemata, it will take care of lifting the per-version schema up to the global schema. \n It will generate output for each \"CRD Version\" (API version of the CRD type itself) , e.g. apiextensions/v1beta1 and apiextensions/v1) available.",
Details: "It will generate output for each \"CRD Version\" (API version of the CRD type itself) , e.g. apiextensions/v1) available.",
},
FieldHelp: map[string]markers.DetailedHelp{
"ManifestsPath": {
@@ -40,6 +41,10 @@ func (Generator) Help() *markers.DefinitionHelp {
Summary: "specifies the maximum description length for fields in CRD's OpenAPI schema. ",
Details: "0 indicates drop the description for all fields completely. n indicates limit the description to at most n characters and truncate the description to closest sentence boundary if it exceeds n characters.",
},
"GenerateEmbeddedObjectMeta": {
Summary: "specifies if any embedded ObjectMeta in the CRD should be generated",
Details: "",
},
},
}
}

View File

@@ -5,7 +5,7 @@ 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
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,
@@ -23,8 +23,8 @@ import (
// Version returns the version of the main module
func Version() string {
info, ok := debug.ReadBuildInfo()
if !ok {
// binary has not been built with module support
if !ok || info == nil || info.Main.Version == "" {
// binary has not been built with module support or doesn't contain a version.
return "(unknown)"
}
return info.Main.Version
@@ -36,10 +36,10 @@ func Version() string {
//
// - "Version: v0.2.1" when the program has been compiled with:
//
// $ go get github.com/controller-tools/cmd/controller-gen@v0.2.1
// $ go get github.com/controller-tools/cmd/controller-gen@v0.2.1
//
// Note: go modules requires the usage of semver compatible tags starting with
// 'v' to have nice human-readable versions.
// Note: go modules requires the usage of semver compatible tags starting with
// 'v' to have nice human-readable versions.
//
// - "Version: (devel)" when the program is compiled from a local git checkout.
//

View File

@@ -19,7 +19,7 @@ limitations under the License.
//
// The markers take the form:
//
// +kubebuilder:webhook:webhookVersions=<[]string>,failurePolicy=<string>,matchPolicy=<string>,groups=<[]string>,resources=<[]string>,verbs=<[]string>,versions=<[]string>,name=<string>,path=<string>,mutating=<bool>,sideEffects=<string>,admissionReviewVersions=<[]string>
// +kubebuilder:webhook:webhookVersions=<[]string>,failurePolicy=<string>,matchPolicy=<string>,groups=<[]string>,resources=<[]string>,verbs=<[]string>,versions=<[]string>,name=<string>,path=<string>,mutating=<bool>,sideEffects=<string>,admissionReviewVersions=<[]string>,reinvocationPolicy=<string>
package webhook
import (
@@ -36,7 +36,8 @@ import (
// The default {Mutating,Validating}WebhookConfiguration version to generate.
const (
defaultWebhookVersion = "v1"
v1 = "v1"
defaultWebhookVersion = v1
)
var (
@@ -47,7 +48,7 @@ var (
// supportedWebhookVersions returns currently supported API version of {Mutating,Validating}WebhookConfiguration.
func supportedWebhookVersions() []string {
return []string{defaultWebhookVersion, "v1beta1"}
return []string{defaultWebhookVersion}
}
// +controllertools:marker:generateHelp:category=Webhook
@@ -104,14 +105,20 @@ type Config struct {
Path string
// WebhookVersions specifies the target API versions of the {Mutating,Validating}WebhookConfiguration objects
// itself to generate. Defaults to v1.
// itself to generate. The only supported value is v1. Defaults to v1.
WebhookVersions []string `marker:"webhookVersions,optional"`
// AdmissionReviewVersions is an ordered list of preferred `AdmissionReview`
// versions the Webhook expects.
// For generating v1 {Mutating,Validating}WebhookConfiguration, this is mandatory.
// For generating v1beta1 {Mutating,Validating}WebhookConfiguration, this is optional, and default to v1beta1.
AdmissionReviewVersions []string `marker:"admissionReviewVersions,optional"`
AdmissionReviewVersions []string `marker:"admissionReviewVersions"`
// ReinvocationPolicy allows mutating webhooks to request reinvocation after other mutations
//
// To allow mutating admission plugins to observe changes made by other plugins,
// built-in mutating admission plugins are re-run if a mutating webhook modifies
// an object, and mutating webhooks can specify a reinvocationPolicy to control
// whether they are reinvoked as well.
ReinvocationPolicy string `marker:"reinvocationPolicy,optional"`
}
// verbToAPIVariant converts a marker's verb to the proper value for the API.
@@ -152,6 +159,7 @@ func (c Config) ToMutatingWebhook() (admissionregv1.MutatingWebhook, error) {
ClientConfig: c.clientConfig(),
SideEffects: c.sideEffects(),
AdmissionReviewVersions: c.AdmissionReviewVersions,
ReinvocationPolicy: c.reinvocationPolicy(),
}, nil
}
@@ -264,6 +272,20 @@ func (c Config) sideEffects() *admissionregv1.SideEffectClass {
return &sideEffects
}
// reinvocationPolicy returns the reinvocationPolicy config for a mutating webhook.
func (c Config) reinvocationPolicy() *admissionregv1.ReinvocationPolicyType {
var reinvocationPolicy admissionregv1.ReinvocationPolicyType
switch strings.ToLower(c.ReinvocationPolicy) {
case strings.ToLower(string(admissionregv1.NeverReinvocationPolicy)):
reinvocationPolicy = admissionregv1.NeverReinvocationPolicy
case strings.ToLower(string(admissionregv1.IfNeededReinvocationPolicy)):
reinvocationPolicy = admissionregv1.IfNeededReinvocationPolicy
default:
return nil
}
return &reinvocationPolicy
}
// webhookVersions returns the target API versions of the {Mutating,Validating}WebhookConfiguration objects for a webhook.
func (c Config) webhookVersions() ([]string, error) {
// If WebhookVersions is not specified, we default it to `v1`.
@@ -331,9 +353,8 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
versionedWebhooks := make(map[string][]interface{}, len(supportedWebhookVersions))
for _, version := range supportedWebhookVersions {
if cfgs, ok := mutatingCfgs[version]; ok {
// All webhook config versions in supportedWebhookVersions have the same general form, with a few
// stricter requirements for v1. Since no conversion scheme exists for webhook configs, the v1
// type can be used for all versioned types in this context.
// The only possible version in supportedWebhookVersions is v1,
// so use it for all versioned types in this context.
objRaw := &admissionregv1.MutatingWebhookConfiguration{}
objRaw.SetGroupVersionKind(schema.GroupVersionKind{
Group: admissionregv1.SchemeGroupVersion.Group,
@@ -342,28 +363,24 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
})
objRaw.SetName("mutating-webhook-configuration")
objRaw.Webhooks = cfgs
switch version {
case admissionregv1.SchemeGroupVersion.Version:
for i := range objRaw.Webhooks {
// SideEffects is required in admissionregistration/v1, if this is not set or set to `Some` or `Known`,
// return an error
if err := checkSideEffectsForV1(objRaw.Webhooks[i].SideEffects); err != nil {
return err
}
// AdmissionReviewVersions is required in admissionregistration/v1, if this is not set,
// return an error
if len(objRaw.Webhooks[i].AdmissionReviewVersions) == 0 {
return fmt.Errorf("AdmissionReviewVersions is mandatory for v1 {Mutating,Validating}WebhookConfiguration")
}
for i := range objRaw.Webhooks {
// SideEffects is required in admissionregistration/v1, if this is not set or set to `Some` or `Known`,
// return an error
if err := checkSideEffectsForV1(objRaw.Webhooks[i].SideEffects); err != nil {
return err
}
// AdmissionReviewVersions is required in admissionregistration/v1, if this is not set,
// return an error
if len(objRaw.Webhooks[i].AdmissionReviewVersions) == 0 {
return fmt.Errorf("AdmissionReviewVersions is mandatory for v1 {Mutating,Validating}WebhookConfiguration")
}
}
versionedWebhooks[version] = append(versionedWebhooks[version], objRaw)
}
if cfgs, ok := validatingCfgs[version]; ok {
// All webhook config versions in supportedWebhookVersions have the same general form, with a few
// stricter requirements for v1. Since no conversion scheme exists for webhook configs, the v1
// type can be used for all versioned types in this context.
// The only possible version in supportedWebhookVersions is v1,
// so use it for all versioned types in this context.
objRaw := &admissionregv1.ValidatingWebhookConfiguration{}
objRaw.SetGroupVersionKind(schema.GroupVersionKind{
Group: admissionregv1.SchemeGroupVersion.Group,
@@ -372,19 +389,16 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
})
objRaw.SetName("validating-webhook-configuration")
objRaw.Webhooks = cfgs
switch version {
case admissionregv1.SchemeGroupVersion.Version:
for i := range objRaw.Webhooks {
// SideEffects is required in admissionregistration/v1, if this is not set or set to `Some` or `Known`,
// return an error
if err := checkSideEffectsForV1(objRaw.Webhooks[i].SideEffects); err != nil {
return err
}
// AdmissionReviewVersions is required in admissionregistration/v1, if this is not set,
// return an error
if len(objRaw.Webhooks[i].AdmissionReviewVersions) == 0 {
return fmt.Errorf("AdmissionReviewVersions is mandatory for v1 {Mutating,Validating}WebhookConfiguration")
}
for i := range objRaw.Webhooks {
// SideEffects is required in admissionregistration/v1, if this is not set or set to `Some` or `Known`,
// return an error
if err := checkSideEffectsForV1(objRaw.Webhooks[i].SideEffects); err != nil {
return err
}
// AdmissionReviewVersions is required in admissionregistration/v1, if this is not set,
// return an error
if len(objRaw.Webhooks[i].AdmissionReviewVersions) == 0 {
return fmt.Errorf("AdmissionReviewVersions is mandatory for v1 {Mutating,Validating}WebhookConfiguration")
}
}
versionedWebhooks[version] = append(versionedWebhooks[version], objRaw)
@@ -398,7 +412,7 @@ func (Generator) Generate(ctx *genall.GenerationContext) error {
} else {
fileName = fmt.Sprintf("manifests.%s.yaml", k)
}
if err := ctx.WriteYAML(fileName, v...); err != nil {
if err := ctx.WriteYAML(fileName, v); err != nil {
return err
}
}

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
@@ -73,13 +74,17 @@ func (Config) Help() *markers.DefinitionHelp {
Details: "",
},
"WebhookVersions": {
Summary: "specifies the target API versions of the {Mutating,Validating}WebhookConfiguration objects itself to generate. Defaults to v1.",
Summary: "specifies the target API versions of the {Mutating,Validating}WebhookConfiguration objects itself to generate. The only supported value is v1. Defaults to v1.",
Details: "",
},
"AdmissionReviewVersions": {
Summary: "is an ordered list of preferred `AdmissionReview` versions the Webhook expects. For generating v1 {Mutating,Validating}WebhookConfiguration, this is mandatory. For generating v1beta1 {Mutating,Validating}WebhookConfiguration, this is optional, and default to v1beta1.",
Summary: "is an ordered list of preferred `AdmissionReview` versions the Webhook expects.",
Details: "",
},
"ReinvocationPolicy": {
Summary: "allows mutating webhooks to request reinvocation after other mutations ",
Details: "To allow mutating admission plugins to observe changes made by other plugins, built-in mutating admission plugins are re-run if a mutating webhook modifies an object, and mutating webhooks can specify a reinvocationPolicy to control whether they are reinvoked as well.",
},
},
}
}