Use apiextension v1

- upgrade from apiextension v1beta1 to v1
 - generate yaml manifest for crd intead of applying it at runtime
  - users will have to apply the manifest with kubectl
 - kg and kgctl log an error if the crd is not present
 - now validation should actually work

Signed-off-by: leonnicolas <leonloechner@gmx.de>
This commit is contained in:
leonnicolas
2021-06-14 09:08:46 +02:00
parent e272d725a5
commit 36643b77b4
584 changed files with 50911 additions and 55838 deletions

58
vendor/sigs.k8s.io/controller-tools/pkg/genall/doc.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package genall defines entrypoints for generation tools to hook into and
// share the same set of parsing, typechecking, and marker information.
//
// 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
//
// Generators output artifacts via an OutputRule. OutputRules know how to
// write output for different package-associated (code) files, as well as
// config files. Each OutputRule should also be considered to be the output
// type as a marker, for easy command-line parsing.
//
// OutputRules groups together an OutputRule per generator, plus a default
// output rule for any not explicitly specified.
//
// OutputRules are defined for stdout, file writing, and sending to /dev/null
// (useful for doing "type-checking" without actually saving the results).
//
// InputRule defines custom input loading, but its shared across all
// Generators. There's currently only a filesystem implementation.
//
// 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
// a handle for reading files (like boilerplate headers).
//
// It will run all associated generators, printing errors and automatically
// skipping type-checking errors (since those are commonly caused by the
// partial type-checking of loader.TypeChecker).
//
// 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
// a registry from command line args.
package genall

View File

@@ -0,0 +1,215 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package genall
import (
"fmt"
"io"
"io/ioutil"
"os"
"golang.org/x/tools/go/packages"
"sigs.k8s.io/yaml"
"sigs.k8s.io/controller-tools/pkg/loader"
"sigs.k8s.io/controller-tools/pkg/markers"
)
// Generators are a list of Generators.
// NB(directxman12): this is a pointer so that we can uniquely identify each
// instance of a generator, even if it's not hashable. Different *instances*
// of a generator are treated differently.
type Generators []*Generator
// RegisterMarkers registers all markers defined by each of the Generators in
// this list into the given registry.
func (g Generators) RegisterMarkers(reg *markers.Registry) error {
for _, gen := range g {
if err := (*gen).RegisterMarkers(reg); err != nil {
return err
}
}
return nil
}
// CheckFilters returns the set of NodeFilters for all Generators that
// implement NeedsTypeChecking.
func (g Generators) CheckFilters() []loader.NodeFilter {
var filters []loader.NodeFilter
for _, gen := range g {
withFilter, needsChecking := (*gen).(NeedsTypeChecking)
if !needsChecking {
continue
}
filters = append(filters, withFilter.CheckFilter())
}
return filters
}
// NeedsTypeChecking indicates that a particular generator needs & has opinions
// on typechecking. If this is not implemented, a generator will be given a
// context with a nil typechecker.
type NeedsTypeChecking interface {
// CheckFilter indicates the loader.NodeFilter (if any) that should be used
// to prune out unused types/packages when type-checking (nodes for which
// the filter returns true are considered "interesting"). This filter acts
// as a baseline -- all types the pass through this filter will be checked,
// but more than that may also be checked due to other generators' filters.
CheckFilter() loader.NodeFilter
}
// Generator knows how to register some set of markers, and then produce
// output artifacts based on loaded code containing those markers,
// sharing common loaded data.
type Generator interface {
// RegisterMarkers registers all markers needed by this Generator
// into the given registry.
RegisterMarkers(into *markers.Registry) error
// Generate generates artifacts produced by this marker.
// It's called *after* RegisterMarkers has been called.
Generate(*GenerationContext) error
}
// HasHelp is some Generator, OutputRule, etc with a help method.
type HasHelp interface {
// Help returns help for this generator.
Help() *markers.DefinitionHelp
}
// Runtime collects generators, loaded program data (Collector, root Packages),
// and I/O rules, running them together.
type Runtime struct {
// Generators are the Generators to be run by this Runtime.
Generators Generators
// GenerationContext is the base generation context that's copied
// to produce the context for each Generator.
GenerationContext
// OutputRules defines how to output artifacts for each Generator.
OutputRules OutputRules
}
// GenerationContext defines the common information needed for each Generator
// to run.
type GenerationContext struct {
// Collector is the shared marker collector.
Collector *markers.Collector
// Roots are the base packages to be processed.
Roots []*loader.Package
// Checker is the shared partial type-checker.
Checker *loader.TypeChecker
// OutputRule describes how to output artifacts.
OutputRule
// InputRule describes how to load associated boilerplate artifacts.
// It should *not* be used to load source files.
InputRule
}
// 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 {
out, err := g.Open(nil, itemPath)
if err != nil {
return err
}
defer out.Close()
for _, obj := range objs {
yamlContent, err := yaml.Marshal(obj)
if err != nil {
return err
}
n, err := out.Write(append([]byte("\n---\n"), yamlContent...))
if err != nil {
return err
}
if n < len(yamlContent) {
return io.ErrShortWrite
}
}
return nil
}
// ReadFile reads the given boilerplate artifact using the context's InputRule.
func (g GenerationContext) ReadFile(path string) ([]byte, error) {
file, err := g.OpenForRead(path)
if err != nil {
return nil, err
}
defer file.Close()
return ioutil.ReadAll(file)
}
// ForRoots produces a Runtime to run the given generators against the
// given packages. It outputs to /dev/null by default.
func (g Generators) ForRoots(rootPaths ...string) (*Runtime, error) {
roots, err := loader.LoadRoots(rootPaths...)
if err != nil {
return nil, err
}
rt := &Runtime{
Generators: g,
GenerationContext: GenerationContext{
Collector: &markers.Collector{
Registry: &markers.Registry{},
},
Roots: roots,
InputRule: InputFromFileSystem,
Checker: &loader.TypeChecker{
NodeFilters: g.CheckFilters(),
},
},
OutputRules: OutputRules{Default: OutputToNothing},
}
if err := rt.Generators.RegisterMarkers(rt.Collector.Registry); err != nil {
return nil, err
}
return rt, nil
}
// Run runs the Generators in this Runtime against its packages, printing
// errors (except type errors, which common result from using TypeChecker with
// filters), returning true if errors were found.
func (r *Runtime) Run() bool {
// TODO(directxman12): we could make this parallel,
// but we'd need to ensure all underlying machinery is threadsafe
if len(r.Generators) == 0 {
fmt.Fprintln(os.Stderr, "no generators to run")
return true
}
hadErrs := false
for _, gen := range r.Generators {
ctx := r.GenerationContext // make a shallow copy
ctx.OutputRule = r.OutputRules.ForGenerator(gen)
// don't pass a typechecker to generators that don't provide a filter
// to avoid accidents
if _, needsChecking := (*gen).(NeedsTypeChecking); !needsChecking {
ctx.Checker = nil
}
if err := (*gen).Generate(&ctx); err != nil {
fmt.Fprintln(os.Stderr, err)
hadErrs = true
}
}
// skip TypeErrors -- they're probably just from partial typechecking in crd-gen
return loader.PrintErrors(r.Roots, packages.TypeError) || hadErrs
}

View File

@@ -0,0 +1,23 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package help contains utilities for actually writing out marker help.
//
// Namely, it contains a series of structs (and helpers for producing them)
// that represent a merged view of marker definition and help that can be used
// for consumption by the pretty subpackage (for terminal help) or serialized
// as JSON (e.g. for generating HTML help).
package help

View File

@@ -0,0 +1,30 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package pretty contains utilities for formatting terminal help output,
// and a use of those to display marker help.
//
// 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
//
// The MarkersSummary prints a summary table for marker help, while the MarkersDetails
// prints out more detailed information, with explainations of the individual marker fields.
package pretty

View File

@@ -0,0 +1,171 @@
package pretty
import (
"fmt"
"io"
"sigs.k8s.io/controller-tools/pkg/genall/help"
"github.com/fatih/color"
)
var (
headingStyle = Decoration(*color.New(color.Bold, color.Underline))
markerNameStyle = Decoration(*color.New(color.Bold))
fieldSummaryStyle = Decoration(*color.New(color.FgGreen, color.Italic))
markerTargetStyle = Decoration(*color.New(color.Faint))
fieldDetailStyle = Decoration(*color.New(color.Italic, color.FgGreen))
deprecatedStyle = Decoration(*color.New(color.CrossedOut))
)
// MarkersSummary returns a condensed summary of help for the given markers.
func MarkersSummary(groupName string, markers []help.MarkerDoc) Span {
out := new(SpanWriter)
out.Print(Text("\n"))
out.Print(headingStyle.Containing(Text(groupName)))
out.Print(Text("\n\n"))
table := &Table{Sizing: &TableCalculator{Padding: 2}}
for _, marker := range markers {
table.StartRow()
table.Column(MarkerSyntaxHelp(marker))
table.Column(markerTargetStyle.Containing(Text(marker.Target)))
summary := new(SpanWriter)
if marker.DeprecatedInFavorOf != nil && len(*marker.DeprecatedInFavorOf) > 0 {
summary.Print(markerNameStyle.Containing(Text("(use ")))
summary.Print(markerNameStyle.Containing(Text(*marker.DeprecatedInFavorOf)))
summary.Print(markerNameStyle.Containing(Text(") ")))
}
summary.Print(Text(marker.Summary))
table.Column(summary)
table.EndRow()
}
out.Print(table)
out.Print(Text("\n"))
return out
}
// MarkersDetails returns detailed help for the given markers, including detailed field help.
func MarkersDetails(fullDetail bool, groupName string, markers []help.MarkerDoc) Span {
out := new(SpanWriter)
out.Print(Line(headingStyle.Containing(Text(groupName))))
out.Print(Newlines(2))
for _, marker := range markers {
out.Print(Line(markerName(marker)))
out.Print(Text(" "))
out.Print(markerTargetStyle.Containing(Text(marker.Target)))
summary := new(SpanWriter)
if marker.DeprecatedInFavorOf != nil && len(*marker.DeprecatedInFavorOf) > 0 {
summary.Print(markerNameStyle.Containing(Text("(use ")))
summary.Print(markerNameStyle.Containing(Text(*marker.DeprecatedInFavorOf)))
summary.Print(markerNameStyle.Containing(Text(") ")))
}
summary.Print(Text(marker.Summary))
if !marker.AnonymousField() {
out.Print(Indented(1, Line(summary)))
if len(marker.Details) > 0 && fullDetail {
out.Print(Indented(1, Line(Text(marker.Details))))
}
}
if marker.AnonymousField() {
out.Print(Indented(1, Line(fieldDetailStyle.Containing(FieldSyntaxHelp(marker.Fields[0])))))
out.Print(Text(" "))
out.Print(summary)
if len(marker.Details) > 0 && fullDetail {
out.Print(Indented(2, Line(Text(marker.Details))))
}
out.Print(Newlines(1))
} else if !marker.Empty() {
out.Print(Newlines(1))
if fullDetail {
for _, arg := range marker.Fields {
out.Print(Indented(1, Line(fieldDetailStyle.Containing(FieldSyntaxHelp(arg)))))
out.Print(Indented(2, Line(Text(arg.Summary))))
if len(arg.Details) > 0 && fullDetail {
out.Print(Indented(2, Line(Text(arg.Details))))
out.Print(Newlines(1))
}
}
out.Print(Newlines(1))
} else {
table := &Table{Sizing: &TableCalculator{Padding: 2}}
for _, arg := range marker.Fields {
table.StartRow()
table.Column(fieldDetailStyle.Containing(FieldSyntaxHelp(arg)))
table.Column(Text(arg.Summary))
table.EndRow()
}
out.Print(Indented(1, table))
}
} else {
out.Print(Newlines(1))
}
}
return out
}
func FieldSyntaxHelp(arg help.FieldHelp) Span {
return fieldSyntaxHelp(arg, "")
}
// fieldSyntaxHelp prints the syntax help for a particular marker argument.
func fieldSyntaxHelp(arg help.FieldHelp, sep string) Span {
if arg.Optional {
return FromWriter(func(out io.Writer) error {
_, err := fmt.Fprintf(out, "[%s%s=<%s>]", sep, arg.Name, arg.TypeString())
return err
})
}
return FromWriter(func(out io.Writer) error {
_, err := fmt.Fprintf(out, "%s%s=<%s>", sep, arg.Name, arg.TypeString())
return err
})
}
// markerName returns a span containing just the appropriately-formatted marker name.
func markerName(def help.MarkerDoc) Span {
if def.DeprecatedInFavorOf != nil {
return deprecatedStyle.Containing(Text("+" + def.Name))
}
return markerNameStyle.Containing(Text("+" + def.Name))
}
// MarkerSyntaxHelp assembles syntax help for a given marker.
func MarkerSyntaxHelp(def help.MarkerDoc) Span {
out := new(SpanWriter)
out.Print(markerName(def))
if def.Empty() {
return out
}
sep := ":"
if def.AnonymousField() {
sep = ""
}
fieldStyle := fieldSummaryStyle
if def.DeprecatedInFavorOf != nil {
fieldStyle = deprecatedStyle
}
for _, arg := range def.Fields {
out.Print(fieldStyle.Containing(fieldSyntaxHelp(arg, sep)))
sep = ","
}
return out
}

View File

@@ -0,0 +1,304 @@
package pretty
import (
"bytes"
"fmt"
"io"
"github.com/fatih/color"
)
// NB(directxman12): this isn't particularly elegant, but it's also
// sufficiently simple as to be maintained here. Man (roff) would've
// probably worked, but it's not necessarily on Windows by default.
// Span is a chunk of content that is writable to an output, but knows how to
// calculate its apparent visual "width" on the terminal (not to be confused
// with the raw length, which may include zero-width coloring sequences).
type Span interface {
// VisualLength reports the "width" as perceived by the user on the terminal
// (i.e. widest line, ignoring ANSI escape characters).
VisualLength() int
// WriteTo writes the full span contents to the given writer.
WriteTo(io.Writer) error
}
// Table is a Span that writes its data in table form, with sizing controlled
// by the given table calculator. Rows are started with StartRow, followed by
// some calls to Column, followed by a call to EndRow. Once all rows are
// added, the table can be used as a Span.
type Table struct {
Sizing *TableCalculator
cellsByRow [][]Span
colSizes []int
}
// StartRow starts a new row.
// It must eventually be followed by EndRow.
func (t *Table) StartRow() {
t.cellsByRow = append(t.cellsByRow, []Span(nil))
}
// EndRow ends the currently started row.
func (t *Table) EndRow() {
lastRow := t.cellsByRow[len(t.cellsByRow)-1]
sizes := make([]int, len(lastRow))
for i, cell := range lastRow {
sizes[i] = cell.VisualLength()
}
t.Sizing.AddRowSizes(sizes...)
}
// Column adds the given span as a new column to the current row.
func (t *Table) Column(contents Span) {
currentRowInd := len(t.cellsByRow) - 1
t.cellsByRow[currentRowInd] = append(t.cellsByRow[currentRowInd], contents)
}
// SkipRow prints a span without having it contribute to the table calculation.
func (t *Table) SkipRow(contents Span) {
t.cellsByRow = append(t.cellsByRow, []Span{contents})
}
func (t *Table) WriteTo(out io.Writer) error {
if t.colSizes == nil {
t.colSizes = t.Sizing.ColumnWidths()
}
for _, cells := range t.cellsByRow {
currentPosition := 0
for colInd, cell := range cells {
colSize := t.colSizes[colInd]
diff := colSize - cell.VisualLength()
if err := cell.WriteTo(out); err != nil {
return err
}
if diff > 0 {
if err := writePadding(out, columnPadding, diff); err != nil {
return err
}
}
currentPosition += colSize
}
if _, err := fmt.Fprint(out, "\n"); err != nil {
return err
}
}
return nil
}
func (t *Table) VisualLength() int {
if t.colSizes == nil {
t.colSizes = t.Sizing.ColumnWidths()
}
res := 0
for _, colSize := range t.colSizes {
res += colSize
}
return res
}
// Text is a span that simply contains raw text. It's a good starting point.
type Text string
func (t Text) VisualLength() int { return len(t) }
func (t Text) WriteTo(w io.Writer) error {
_, err := w.Write([]byte(t))
return err
}
// indented is a span that indents all lines by the given number of tabs.
type indented struct {
Amount int
Content Span
}
func (i *indented) VisualLength() int { return i.Content.VisualLength() }
func (i *indented) WriteTo(w io.Writer) error {
var out bytes.Buffer
if err := i.Content.WriteTo(&out); err != nil {
return err
}
lines := bytes.Split(out.Bytes(), []byte("\n"))
for lineInd, line := range lines {
if lineInd != 0 {
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
}
if len(line) == 0 {
continue
}
if err := writePadding(w, indentPadding, i.Amount); err != nil {
return err
}
if _, err := w.Write(line); err != nil {
return err
}
}
return nil
}
// Indented returns a span that indents all lines by the given number of tabs.
func Indented(amt int, content Span) Span {
return &indented{Amount: amt, Content: content}
}
// fromWriter is a span that takes content from a function expecting a Writer.
type fromWriter struct {
cache []byte
cacheError error
run func(io.Writer) error
}
func (f *fromWriter) VisualLength() int {
if f.cache == nil {
var buf bytes.Buffer
if err := f.run(&buf); err != nil {
f.cacheError = err
}
f.cache = buf.Bytes()
}
return len(f.cache)
}
func (f *fromWriter) WriteTo(w io.Writer) error {
if f.cache != nil {
if f.cacheError != nil {
return f.cacheError
}
_, err := w.Write(f.cache)
return err
}
return f.run(w)
}
// FromWriter returns a span that takes content from a function expecting a Writer.
func FromWriter(run func(io.Writer) error) Span {
return &fromWriter{run: run}
}
// Decoration represents a terminal decoration.
type Decoration color.Color
// Containing returns a Span that has the given decoration applied.
func (d Decoration) Containing(contents Span) Span {
return &decorated{
Contents: contents,
Attributes: color.Color(d),
}
}
// decorated is a span that has some terminal decoration applied.
type decorated struct {
Contents Span
Attributes color.Color
}
func (d *decorated) VisualLength() int { return d.Contents.VisualLength() }
func (d *decorated) WriteTo(w io.Writer) error {
oldOut := color.Output
color.Output = w
defer func() { color.Output = oldOut }()
d.Attributes.Set()
defer color.Unset()
return d.Contents.WriteTo(w)
}
// SpanWriter is a span that contains multiple sub-spans.
type SpanWriter struct {
contents []Span
}
func (m *SpanWriter) VisualLength() int {
res := 0
for _, span := range m.contents {
res += span.VisualLength()
}
return res
}
func (m *SpanWriter) WriteTo(w io.Writer) error {
for _, span := range m.contents {
if err := span.WriteTo(w); err != nil {
return err
}
}
return nil
}
// Print adds a new span to this SpanWriter.
func (m *SpanWriter) Print(s Span) {
m.contents = append(m.contents, s)
}
// lines is a span that adds some newlines, optionally followed by some content.
type lines struct {
content Span
amountBefore int
}
func (l *lines) VisualLength() int {
if l.content == nil {
return 0
}
return l.content.VisualLength()
}
func (l *lines) WriteTo(w io.Writer) error {
if err := writePadding(w, linesPadding, l.amountBefore); err != nil {
return err
}
if l.content != nil {
if err := l.content.WriteTo(w); err != nil {
return err
}
}
return nil
}
// Newlines returns a span just containing some newlines.
func Newlines(amt int) Span {
return &lines{amountBefore: amt}
}
// Line returns a span that emits a newline, followed by the given content.
func Line(content Span) Span {
return &lines{amountBefore: 1, content: content}
}
var (
columnPadding = []byte(" ")
indentPadding = []byte("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t")
linesPadding = []byte("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
)
// writePadding writes out padding of the given type in the given amount to the writer.
// Each byte in the padding buffer contributes 1 to the amount -- the padding being
// a buffer is just for efficiency.
func writePadding(out io.Writer, typ []byte, amt int) error {
if amt <= len(typ) {
_, err := out.Write(typ[:amt])
return err
}
num := amt / len(typ)
rem := amt % len(typ)
for i := 0; i < num; i++ {
if _, err := out.Write(typ); err != nil {
return err
}
}
if _, err := out.Write(typ[:rem]); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,64 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pretty
// TableCalculator calculates column widths (with optional padding)
// for a table based on the maximum required column width.
type TableCalculator struct {
cellSizesByCol [][]int
Padding int
MaxWidth int
}
// AddRowSizes registers a new row with cells of the given sizes.
func (c *TableCalculator) AddRowSizes(cellSizes ...int) {
if len(cellSizes) > len(c.cellSizesByCol) {
for range cellSizes[len(c.cellSizesByCol):] {
c.cellSizesByCol = append(c.cellSizesByCol, []int(nil))
}
}
for i, size := range cellSizes {
c.cellSizesByCol[i] = append(c.cellSizesByCol[i], size)
}
}
// ColumnWidths calculates the appropriate column sizes given the
// previously registered rows.
func (c *TableCalculator) ColumnWidths() []int {
maxColWidths := make([]int, len(c.cellSizesByCol))
for colInd, cellSizes := range c.cellSizesByCol {
max := 0
for _, cellSize := range cellSizes {
if max < cellSize {
max = cellSize
}
}
maxColWidths[colInd] = max
}
actualMaxWidth := c.MaxWidth - c.Padding
for i, width := range maxColWidths {
if actualMaxWidth > 0 && width > actualMaxWidth {
maxColWidths[i] = actualMaxWidth
}
maxColWidths[i] += c.Padding
}
return maxColWidths
}

View File

@@ -0,0 +1,106 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package help
import (
"strings"
"sigs.k8s.io/controller-tools/pkg/markers"
)
// SortGroup knows how to sort and group marker definitions.
type SortGroup interface {
// Less is equivalent to the Less function from sort, and is used to sort the markers.
Less(*markers.Definition, *markers.Definition) bool
// Group returns the "group" that a given marker belongs to.
Group(*markers.Definition, *markers.DefinitionHelp) string
}
var (
// SortByCategory sorts the markers by name and groups them by their help category.
SortByCategory = sortByCategory{}
// SortByOption sorts by the generator that the option belongs to.
SortByOption = optionsSort{}
)
type sortByCategory struct{}
func (sortByCategory) Group(_ *markers.Definition, help *markers.DefinitionHelp) string {
if help == nil {
return ""
}
return help.Category
}
func (sortByCategory) Less(i, j *markers.Definition) bool {
return i.Name < j.Name
}
type optionsSort struct{}
func (optionsSort) Less(i, j *markers.Definition) bool {
iParts := strings.Split(i.Name, ":")
jParts := strings.Split(j.Name, ":")
iGen := ""
iRule := ""
jGen := ""
jRule := ""
switch len(iParts) {
case 1:
iGen = iParts[0]
// two means a default output rule, so ignore
case 2:
iRule = iParts[1]
case 3:
iGen = iParts[1]
iRule = iParts[2]
}
switch len(jParts) {
case 1:
jGen = jParts[0]
// two means a default output rule, so ignore
case 2:
jRule = jParts[1]
case 3:
jGen = jParts[1]
jRule = jParts[2]
}
if iGen != jGen {
return iGen > jGen
}
return iRule < jRule
}
func (optionsSort) Group(def *markers.Definition, _ *markers.DefinitionHelp) string {
parts := strings.Split(def.Name, ":")
switch len(parts) {
case 1:
if parts[0] == "paths" {
return "generic"
}
return "generators"
case 2:
return "output rules (optionally as output:<generator>:...)"
default:
return ""
// three means a marker-specific output rule, ignore
}
}

View File

@@ -0,0 +1,215 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package help
import (
"sort"
"strings"
"sigs.k8s.io/controller-tools/pkg/markers"
)
// DetailedHelp contains both a summary and further details.
type DetailedHelp struct {
// Summary contains a one-line description.
Summary string `json:"summary"`
// Details contains further information.
Details string `json:"details,omitempty"`
}
// Argument is the type data for a marker argument.
type Argument struct {
// Type is the data type of the argument (string, bool, int, slice, any, raw, invalid)
Type string `json:"type"`
// Optional marks this argument as optional.
Optional bool `json:"optional"`
// ItemType contains the type of the slice item, if this is a slice
ItemType *Argument `json:"itemType,omitempty"`
}
func (a Argument) typeString(out *strings.Builder) {
if a.Type == "slice" {
out.WriteString("[]")
a.ItemType.typeString(out)
return
}
out.WriteString(a.Type)
}
// TypeString returns a string roughly equivalent
// (but not identical) to the underlying Go type that
// this argument would parse to. It's mainly useful
// for user-friendly formatting of this argument (e.g.
// help strings).
func (a Argument) TypeString() string {
out := &strings.Builder{}
a.typeString(out)
return out.String()
}
// FieldHelp contains information required to print documentation for a marker field.
type FieldHelp struct {
// Name is the field name.
Name string `json:"name"`
// Argument is the type of the field.
Argument `json:",inline"`
// DetailedHelp contains the textual help for the field.
DetailedHelp `json:",inline"`
}
// MarkerDoc contains information required to print documentation for a marker.
type MarkerDoc struct {
// definition
// Name is the name of the marker.
Name string `json:"name"`
// Target is the target (field, package, type) of the marker.
Target string `json:"target"`
// help
// DetailedHelp is the textual help for the marker.
DetailedHelp `json:",inline"`
// Category is the general "category" that this marker belongs to.
Category string `json:"category"`
// DeprecatedInFavorOf marks that this marker shouldn't be used when
// non-nil. If also non-empty, another marker should be used instead.
DeprecatedInFavorOf *string `json:"deprecatedInFavorOf,omitempty"`
// Fields is the type and help data for each field of this marker.
Fields []FieldHelp `json:"fields,omitempty"`
}
// Empty checks if this marker has any arguments, returning true if not.
func (m MarkerDoc) Empty() bool {
return len(m.Fields) == 0
}
// AnonymousField chekcs if this is an single-valued marker
// (as opposed to having named fields).
func (m MarkerDoc) AnonymousField() bool {
return len(m.Fields) == 1 && m.Fields[0].Name == ""
}
// ForArgument returns the equivalent documentation for a marker argument.
func ForArgument(argRaw markers.Argument) Argument {
res := Argument{
Optional: argRaw.Optional,
}
if argRaw.ItemType != nil {
itemType := ForArgument(*argRaw.ItemType)
res.ItemType = &itemType
}
switch argRaw.Type {
case markers.IntType:
res.Type = "int"
case markers.StringType:
res.Type = "string"
case markers.BoolType:
res.Type = "bool"
case markers.AnyType:
res.Type = "any"
case markers.SliceType:
res.Type = "slice"
case markers.RawType:
res.Type = "raw"
case markers.InvalidType:
res.Type = "invalid"
}
return res
}
// ForDefinition returns the equivalent marker documentation for a given marker definition and spearate help.
func ForDefinition(defn *markers.Definition, maybeHelp *markers.DefinitionHelp) MarkerDoc {
var help markers.DefinitionHelp
if maybeHelp != nil {
help = *maybeHelp
}
res := MarkerDoc{
Name: defn.Name,
Category: help.Category,
DeprecatedInFavorOf: help.DeprecatedInFavorOf,
Target: defn.Target.String(),
DetailedHelp: DetailedHelp{Summary: help.Summary, Details: help.Details},
}
helpByField := help.FieldsHelp(defn)
// TODO(directxman12): deterministic ordering
for fieldName, fieldHelpRaw := range helpByField {
fieldInfo := defn.Fields[fieldName]
fieldHelp := FieldHelp{
Name: fieldName,
DetailedHelp: DetailedHelp{Summary: fieldHelpRaw.Summary, Details: fieldHelpRaw.Details},
Argument: ForArgument(fieldInfo),
}
res.Fields = append(res.Fields, fieldHelp)
}
sort.Slice(res.Fields, func(i, j int) bool { return res.Fields[i].Name < res.Fields[j].Name })
return res
}
// CategoryDoc contains help information for all markers in a Category.
type CategoryDoc struct {
Category string `json:"category"`
Markers []MarkerDoc `json:"markers"`
}
// ByCategory returns the marker help for markers in the given
// registry, grouped and sorted according to the given method.
func ByCategory(reg *markers.Registry, sorter SortGroup) []CategoryDoc {
groupedMarkers := make(map[string][]*markers.Definition)
for _, marker := range reg.AllDefinitions() {
group := sorter.Group(marker, reg.HelpFor(marker))
groupedMarkers[group] = append(groupedMarkers[group], marker)
}
allGroups := make([]string, 0, len(groupedMarkers))
for groupName := range groupedMarkers {
allGroups = append(allGroups, groupName)
}
sort.Strings(allGroups)
res := make([]CategoryDoc, len(allGroups))
for i, groupName := range allGroups {
markers := groupedMarkers[groupName]
sort.Slice(markers, func(i, j int) bool {
return sorter.Less(markers[i], markers[j])
})
markerDocs := make([]MarkerDoc, len(markers))
for i, marker := range markers {
markerDocs[i] = ForDefinition(marker, reg.HelpFor(marker))
}
res[i] = CategoryDoc{
Category: groupName,
Markers: markerDocs,
}
}
return res
}

View File

@@ -0,0 +1,37 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package genall
import (
"io"
"os"
)
// InputRule describes how to load non-code boilerplate artifacts.
// It's not used for loading code.
type InputRule interface {
// OpenForRead opens the given non-code artifact for reading.
OpenForRead(path string) (io.ReadCloser, error)
}
type inputFromFileSystem struct{}
func (inputFromFileSystem) OpenForRead(path string) (io.ReadCloser, error) {
return os.Open(path)
}
// InputFromFileSystem reads from the filesystem as normal.
var InputFromFileSystem = inputFromFileSystem{}

View File

@@ -0,0 +1,192 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package genall
import (
"fmt"
"strings"
"sigs.k8s.io/controller-tools/pkg/markers"
)
var (
InputPathsMarker = markers.Must(markers.MakeDefinition("paths", markers.DescribesPackage, InputPaths(nil)))
)
// +controllertools:marker:generateHelp:category=""
// InputPaths represents paths and go-style path patterns to use as package roots.
type InputPaths []string
// RegisterOptionsMarkers registers "mandatory" options markers for FromOptions into the given registry.
// At this point, that's just InputPaths.
func RegisterOptionsMarkers(into *markers.Registry) error {
if err := into.Register(InputPathsMarker); err != nil {
return err
}
// NB(directxman12): we make this optional so we don't have a bootstrap problem with helpgen
if helpGiver, hasHelp := ((interface{})(InputPaths(nil))).(HasHelp); hasHelp {
into.AddHelp(InputPathsMarker, helpGiver.Help())
}
return nil
}
// RegistryFromOptions produces just the marker registry that would be used by FromOptions, without
// attempting to produce a full Runtime. This can be useful if you want to display help without
// trying to load roots.
func RegistryFromOptions(optionsRegistry *markers.Registry, options []string) (*markers.Registry, error) {
protoRt, err := protoFromOptions(optionsRegistry, options)
if err != nil {
return nil, err
}
reg := &markers.Registry{}
if err := protoRt.Generators.RegisterMarkers(reg); err != nil {
return nil, err
}
return reg, nil
}
// FromOptions parses the options from markers stored in the given registry out into a runtime.
// The markers in the registry must be either
//
// a) Generators
// b) OutputRules
// c) InputPaths
//
// The paths specified in InputPaths are loaded as package roots, and the combined with
// the generators and the specified output rules to produce a runtime that can be run or
// further modified. Not default generators are used if none are specified -- you can check
// the output and rerun for that.
func FromOptions(optionsRegistry *markers.Registry, options []string) (*Runtime, error) {
protoRt, err := protoFromOptions(optionsRegistry, options)
if err != nil {
return nil, err
}
// make the runtime
genRuntime, err := protoRt.Generators.ForRoots(protoRt.Paths...)
if err != nil {
return nil, err
}
// attempt to figure out what the user wants without a lot of verbose specificity:
// if the user specifies a default rule, assume that they probably want to fall back
// to that. Otherwise, assume that they just wanted to customize one option from the
// set, and leave the rest in the standard configuration.
if protoRt.OutputRules.Default != nil {
genRuntime.OutputRules = protoRt.OutputRules
return genRuntime, nil
}
outRules := DirectoryPerGenerator("config", protoRt.GeneratorsByName)
for gen, rule := range protoRt.OutputRules.ByGenerator {
outRules.ByGenerator[gen] = rule
}
genRuntime.OutputRules = outRules
return genRuntime, nil
}
// protoFromOptions returns a proto-Runtime from the given options registry and
// options set. This can then be used to construct an actual Runtime. See the
// FromOptions function for more details about how the options work.
func protoFromOptions(optionsRegistry *markers.Registry, options []string) (protoRuntime, error) {
var gens Generators
rules := OutputRules{
ByGenerator: make(map[*Generator]OutputRule),
}
var paths []string
// collect the generators first, so that we can key the output on the actual
// generator, which matters if there's settings in the gen object and it's not a pointer.
outputByGen := make(map[string]OutputRule)
gensByName := make(map[string]*Generator)
for _, rawOpt := range options {
if rawOpt[0] != '+' {
rawOpt = "+" + rawOpt // add a `+` to make it acceptable for usage with the registry
}
defn := optionsRegistry.Lookup(rawOpt, markers.DescribesPackage)
if defn == nil {
return protoRuntime{}, fmt.Errorf("unknown option %q", rawOpt[1:])
}
val, err := defn.Parse(rawOpt)
if err != nil {
return protoRuntime{}, fmt.Errorf("unable to parse option %q: %w", rawOpt[1:], err)
}
switch val := val.(type) {
case Generator:
gens = append(gens, &val)
gensByName[defn.Name] = &val
case OutputRule:
_, genName := splitOutputRuleOption(defn.Name)
if genName == "" {
// it's a default rule
rules.Default = val
continue
}
outputByGen[genName] = val
continue
case InputPaths:
paths = append(paths, val...)
default:
return protoRuntime{}, fmt.Errorf("unknown option marker %q", defn.Name)
}
}
// actually associate the rules now that we know the generators
for genName, outputRule := range outputByGen {
gen, knownGen := gensByName[genName]
if !knownGen {
return protoRuntime{}, fmt.Errorf("non-invoked generator %q", genName)
}
rules.ByGenerator[gen] = outputRule
}
return protoRuntime{
Paths: paths,
Generators: Generators(gens),
OutputRules: rules,
GeneratorsByName: gensByName,
}, nil
}
// protoRuntime represents the raw pieces needed to compose a runtime, as
// parsed from some options.
type protoRuntime struct {
Paths []string
Generators Generators
OutputRules OutputRules
GeneratorsByName map[string]*Generator
}
// splitOutputRuleOption splits a marker name of "output:rule:gen" or "output:rule"
// into its compontent rule and generator name.
func splitOutputRuleOption(name string) (ruleName string, genName string) {
parts := strings.SplitN(name, ":", 3)
if len(parts) == 3 {
// output:<generator>:<rule>
return parts[2], parts[1]
}
// output:<rule>
return parts[1], ""
}

View File

@@ -0,0 +1,160 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package genall
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sigs.k8s.io/controller-tools/pkg/loader"
)
// nopCloser is a WriteCloser whose Close
// is a no-op.
type nopCloser struct {
io.Writer
}
func (n nopCloser) Close() error {
return nil
}
// DirectoryPerGenerator produces output rules mapping output to a different subdirectory
// of the given base directory for each generator (with each subdirectory specified as
// the key in the input map).
func DirectoryPerGenerator(base string, generators map[string]*Generator) OutputRules {
rules := OutputRules{
Default: OutputArtifacts{Config: OutputToDirectory(base)},
ByGenerator: make(map[*Generator]OutputRule, len(generators)),
}
for name, gen := range generators {
rules.ByGenerator[gen] = OutputArtifacts{
Config: OutputToDirectory(filepath.Join(base, name)),
}
}
return rules
}
// OutputRules defines how to output artificats on a per-generator basis.
type OutputRules struct {
// Default is the output rule used when no specific per-generator overrides match.
Default OutputRule
// ByGenerator contains specific per-generator overrides.
// NB(directxman12): this is a pointer to avoid issues if a given Generator becomes unhashable
// (interface values compare by "dereferencing" their internal pointer first, whereas pointers
// compare by the actual pointer itself).
ByGenerator map[*Generator]OutputRule
}
// ForGenerator returns the output rule that should be used
// by the given Generator.
func (o OutputRules) ForGenerator(gen *Generator) OutputRule {
if forGen, specific := o.ByGenerator[gen]; specific {
return forGen
}
return o.Default
}
// OutputRule defines how to output artifacts from a generator.
type OutputRule interface {
// Open opens the given artifact path for writing. If a package is passed,
// the artifact is considered to be used as part of the package (e.g.
// generated code), while a nil package indicates that the artifact is
// config (or something else not involved in Go compilation).
Open(pkg *loader.Package, path string) (io.WriteCloser, error)
}
// OutputToNothing skips outputting anything.
var OutputToNothing = outputToNothing{}
// +controllertools:marker:generateHelp:category=""
// outputToNothing skips outputting anything.
type outputToNothing struct{}
func (o outputToNothing) Open(_ *loader.Package, _ string) (io.WriteCloser, error) {
return nopCloser{ioutil.Discard}, nil
}
// +controllertools:marker:generateHelp:category=""
// OutputToDirectory outputs each artifact to the given directory, regardless
// of if it's package-associated or not.
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 {
return nil, err
}
path := filepath.Join(string(o), itemPath)
return os.Create(path)
}
// OutputToStdout outputs everything to standard-out, with no separation.
//
// Generally useful for single-artifact outputs.
var OutputToStdout = outputToStdout{}
// +controllertools:marker:generateHelp:category=""
// outputToStdout outputs everything to standard-out, with no separation.
//
// Generally useful for single-artifact outputs.
type outputToStdout struct{}
func (o outputToStdout) Open(_ *loader.Package, itemPath string) (io.WriteCloser, error) {
return nopCloser{os.Stdout}, nil
}
// +controllertools:marker:generateHelp:category=""
// OutputArtifacts outputs artifacts to different locations, depending on
// whether they're package-associated or not.
//
// Non-package associated artifacts
// are output to the Config directory, while package-associated ones are output
// to their package's source files' directory, unless an alternate path is
// specified in Code.
type OutputArtifacts struct {
// Config points to the directory to which to write configuration.
Config OutputToDirectory
// Code overrides the directory in which to write new code (defaults to where the existing code lives).
Code OutputToDirectory `marker:",optional"`
}
func (o OutputArtifacts) Open(pkg *loader.Package, itemPath string) (io.WriteCloser, error) {
if pkg == nil {
return o.Config.Open(pkg, itemPath)
}
if o.Code != "" {
return o.Code.Open(pkg, itemPath)
}
if len(pkg.CompiledGoFiles) == 0 {
return nil, fmt.Errorf("cannot output to a package with no path on disk")
}
outDir := filepath.Dir(pkg.CompiledGoFiles[0])
outPath := filepath.Join(outDir, itemPath)
return os.Create(outPath)
}

View File

@@ -0,0 +1,89 @@
// +build !ignore_autogenerated
/*
Copyright2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by helpgen. DO NOT EDIT.
package genall
import (
"sigs.k8s.io/controller-tools/pkg/markers"
)
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: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
}
func (OutputArtifacts) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "",
DetailedHelp: markers.DetailedHelp{
Summary: "outputs artifacts to different locations, depending on whether they're package-associated or not. ",
Details: "Non-package associated artifacts are output to the Config directory, while package-associated ones are output to their package's source files' directory, unless an alternate path is specified in Code.",
},
FieldHelp: map[string]markers.DetailedHelp{
"Config": {
Summary: "points to the directory to which to write configuration.",
Details: "",
},
"Code": {
Summary: "overrides the directory in which to write new code (defaults to where the existing code lives).",
Details: "",
},
},
}
}
func (OutputToDirectory) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "",
DetailedHelp: markers.DetailedHelp{
Summary: "outputs each artifact to the given directory, regardless of if it's package-associated or not.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
}
func (outputToNothing) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "",
DetailedHelp: markers.DetailedHelp{
Summary: "skips outputting anything.",
Details: "",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
}
func (outputToStdout) Help() *markers.DefinitionHelp {
return &markers.DefinitionHelp{
Category: "",
DetailedHelp: markers.DetailedHelp{
Summary: "outputs everything to standard-out, with no separation. ",
Details: "Generally useful for single-artifact outputs.",
},
FieldHelp: map[string]markers.DetailedHelp{},
}
}