From b34eecc2647fb555fd3997ea9aad56504fbd6679 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Sat, 11 Nov 2023 01:18:02 -0500 Subject: [PATCH 01/20] learning --- cmd/cue/cmd/get_crd.go | 73 +++++++++++++++++++++++++++++++++++ cue/build/file.go | 9 +++-- encoding/crd/crd.go | 65 +++++++++++++++++++++++++++++++ encoding/crd/decode.go | 40 +++++++++++++++++++ internal/encoding/encoding.go | 14 +++++++ 5 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 cmd/cue/cmd/get_crd.go create mode 100644 encoding/crd/crd.go create mode 100644 encoding/crd/decode.go diff --git a/cmd/cue/cmd/get_crd.go b/cmd/cue/cmd/get_crd.go new file mode 100644 index 00000000000..86541aab4c7 --- /dev/null +++ b/cmd/cue/cmd/get_crd.go @@ -0,0 +1,73 @@ +// Copyright 2018 The CUE 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 cmd + +import ( + "cuelang.org/go/cue/build" + "cuelang.org/go/cue/load" + "github.com/spf13/cobra" +) + +func newCrdCmd(c *Command) *cobra.Command { + cmd := &cobra.Command{ + Use: "crd [files]", + Short: "add Kubernetes CustomResourceDefinition dependencies to the current module", + Long: `crd converts Kubernetes resources defined by a CustomResourceDefinition into CUE definitions + +The command "cue get crd" converts the Kubernetes CustomResourceDefinition +to CUE. The retrieved definitions are put in the CUE module's pkg +directory at the API group name of the corresponding resource. The converted +definitions are available to any CUE file within the CUE module by using +this name. + +The CustomResourceDefinition is converted to CUE based on how it would be +interpreted by the Kubernetes API server. Definitions for a CRD with group +name "myresource.example.com" and version "v1" will be written to a CUE +file named myresource.example.com/v1/types_crd_gen.cue. + +It is safe for users to add additional files to the generated directories, +as long as their name does not end with _gen.*. + + +Rules of Converting CustomResourceDefinitions to CUE + +CustomResourceDefinitions are converted to cue structs adhering to the following conventions: + + - OpenAPIv3 schema is imported the same as "cue import openapi". + + - The @x-kubernetes-validation attribute is added if the field utilizes the "x-kubernetes-validation" extension. +`, + + RunE: mkRunE(c, extract), + } + + return cmd +} + +func runGetCRD(cmd *Command, args []string) error { + c := &config{ + fileFilter: `\.(json|yaml|yml|jsonl|ldjson)$`, + interpretation: build.CustomResourceDefinition, + encoding: "yaml", + loadCfg: &load.Config{DataFiles: true}, + } + + p, err := newBuildPlan(cmd, c) + if err != nil { + return err + } + + return nil +} diff --git a/cue/build/file.go b/cue/build/file.go index 7b22d2eda68..e7f618e8a6c 100644 --- a/cue/build/file.go +++ b/cue/build/file.go @@ -66,10 +66,11 @@ const ( // the info.title and info.version fields. // // In all other cases, the underlying data is interpreted as is. - Auto Interpretation = "auto" - JSONSchema Interpretation = "jsonschema" - OpenAPI Interpretation = "openapi" - ProtobufJSON Interpretation = "pb" + Auto Interpretation = "auto" + JSONSchema Interpretation = "jsonschema" + OpenAPI Interpretation = "openapi" + ProtobufJSON Interpretation = "pb" + CustomResourceDefinition Interpretation = "crd" ) // A Form specifies the form in which a program should be represented. diff --git a/encoding/crd/crd.go b/encoding/crd/crd.go new file mode 100644 index 00000000000..9bce3cdbe2b --- /dev/null +++ b/encoding/crd/crd.go @@ -0,0 +1,65 @@ +package crd + +import ( + "cuelang.org/go/cue" + "cuelang.org/go/encoding/openapi" +) + +// A Config defines options for converting CUE to and from Kubernetes CustomResourceDefinitions. +type Config struct { + OpenAPICfg *openapi.Config + + // PkgName defines the package name for a generated CUE package. + PkgName string + + // Info specifies the info section of the OpenAPI document. To be a valid + // OpenAPI document, it must include at least the title and version fields. + // Info may be a *ast.StructLit or any type that marshals to JSON. + Info interface{} + + // ReferenceFunc allows users to specify an alternative representation + // for references. An empty string tells the generator to expand the type + // in place and, if applicable, not generate a schema for that entity. + // + // If this field is non-nil and a cue.Value is passed as the InstanceOrValue, + // there will be a panic. + // + // Deprecated: use NameFunc instead. + ReferenceFunc func(inst *cue.Instance, path []string) string + + // NameFunc allows users to specify an alternative representation + // for references. It is called with the value passed to the top level + // method or function and the path to the entity being generated. + // If it returns an empty string the generator will expand the type + // in place and, if applicable, not generate a schema for that entity. + // + // Note: this only returns the final element of the /-separated + // reference. + NameFunc func(val cue.Value, path cue.Path) string + + // DescriptionFunc allows rewriting a description associated with a certain + // field. A typical implementation compiles the description from the + // comments obtains from the Doc method. No description field is added if + // the empty string is returned. + DescriptionFunc func(v cue.Value) string + + // SelfContained causes all non-expanded external references to be included + // in this document. + SelfContained bool + + // OpenAPI version to use. Supported as of v3.0.0. + Version string + + // FieldFilter defines a regular expression of all fields to omit from the + // output. It is only allowed to filter fields that add additional + // constraints. Fields that indicate basic types cannot be removed. It is + // an error for such fields to be excluded by this filter. + // Fields are qualified by their Object type. For instance, the + // minimum field of the schema object is qualified as Schema/minimum. + FieldFilter string + + // ExpandReferences replaces references with actual objects when generating + // OpenAPI Schema. It is an error for an CUE value to refer to itself + // if this option is used. + ExpandReferences bool +} diff --git a/encoding/crd/decode.go b/encoding/crd/decode.go new file mode 100644 index 00000000000..e029ca47640 --- /dev/null +++ b/encoding/crd/decode.go @@ -0,0 +1,40 @@ +package crd + +import ( + "strings" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/ast" + "cuelang.org/go/cue/errors" + "cuelang.org/go/cue/token" + "cuelang.org/go/encoding/openapi" + "cuelang.org/go/internal" +) + +// Extract converts CustomResourceDefinitions to an equivalent CUE representation. +// +// It currently converts entries in spec.versions[*].openApiV3. +func Extract(data cue.InstanceOrValue, c *Config) (*ast.File, error) { + return openapi.Extract(data, c.OpenAPICfg) +} + +const oapiSchemas = "#/components/schemas/" + +// rootDefs is the fallback for schemas that are not valid identifiers. +// TODO: find something more principled. +const rootDefs = "#SchemaMap" + +func openAPIMapping(pos token.Pos, a []string) ([]ast.Label, error) { + if len(a) != 3 || a[0] != "components" || a[1] != "schemas" { + return nil, errors.Newf(pos, + `openapi: reference must be of the form %q; found "#/%s"`, + oapiSchemas, strings.Join(a, "/")) + } + name := a[2] + if ast.IsValidIdent(name) && + name != rootDefs[1:] && + !internal.IsDefOrHidden(name) { + return []ast.Label{ast.NewIdent("#" + name)}, nil + } + return []ast.Label{ast.NewIdent(rootDefs), ast.NewString(name)}, nil +} diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go index 53020fac0c5..162ab806963 100644 --- a/internal/encoding/encoding.go +++ b/internal/encoding/encoding.go @@ -33,6 +33,7 @@ import ( "cuelang.org/go/cue/literal" "cuelang.org/go/cue/parser" "cuelang.org/go/cue/token" + "cuelang.org/go/encoding/crd" "cuelang.org/go/encoding/json" "cuelang.org/go/encoding/jsonschema" "cuelang.org/go/encoding/openapi" @@ -224,6 +225,9 @@ func NewDecoder(f *build.File, cfg *Config) *Decoder { case build.ProtobufJSON: i.interpretation = build.ProtobufJSON i.rewriteFunc = protobufJSONFunc(cfg, f) + case build.CustomResourceDefinition: + i.interpretation = build.CustomResourceDefinition + i.rewriteFunc = customResourceDefinitionFunc(cfg, f) default: i.err = fmt.Errorf("unsupported interpretation %q", f.Interpretation) } @@ -324,6 +328,16 @@ func protobufJSONFunc(cfg *Config, file *build.File) rewriteFunc { } } +func customResourceDefinitionFunc(c *Config, f *build.File) interpretFunc { + cfg := &crd.Config{PkgName: c.PkgName} + return func(i *cue.Instance) (file *ast.File, id string, err error) { + file, err = crd.Extract(i, cfg) + // TODO: simplify currently erases file line info. Reintroduce after fix. + // file, err = simplify(file, err) + return file, "", err + } +} + func reader(f *build.File, stdin io.Reader) (io.ReadCloser, error) { switch s := f.Source.(type) { case nil: From d14859e70d1913735571c4563faca22296c2d37c Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Mon, 13 Nov 2023 16:45:53 -0500 Subject: [PATCH 02/20] pull in timoni importer --- encoding/crd/crd.go | 65 ------ encoding/crd/decode.go | 40 ---- encoding/crd/extract.go | 392 ++++++++++++++++++++++++++++++++++ encoding/crd/unmarshal.go | 62 ++++++ go.mod | 28 ++- go.sum | 112 +++++++++- internal/encoding/encoding.go | 21 +- 7 files changed, 593 insertions(+), 127 deletions(-) delete mode 100644 encoding/crd/crd.go delete mode 100644 encoding/crd/decode.go create mode 100644 encoding/crd/extract.go create mode 100644 encoding/crd/unmarshal.go diff --git a/encoding/crd/crd.go b/encoding/crd/crd.go deleted file mode 100644 index 9bce3cdbe2b..00000000000 --- a/encoding/crd/crd.go +++ /dev/null @@ -1,65 +0,0 @@ -package crd - -import ( - "cuelang.org/go/cue" - "cuelang.org/go/encoding/openapi" -) - -// A Config defines options for converting CUE to and from Kubernetes CustomResourceDefinitions. -type Config struct { - OpenAPICfg *openapi.Config - - // PkgName defines the package name for a generated CUE package. - PkgName string - - // Info specifies the info section of the OpenAPI document. To be a valid - // OpenAPI document, it must include at least the title and version fields. - // Info may be a *ast.StructLit or any type that marshals to JSON. - Info interface{} - - // ReferenceFunc allows users to specify an alternative representation - // for references. An empty string tells the generator to expand the type - // in place and, if applicable, not generate a schema for that entity. - // - // If this field is non-nil and a cue.Value is passed as the InstanceOrValue, - // there will be a panic. - // - // Deprecated: use NameFunc instead. - ReferenceFunc func(inst *cue.Instance, path []string) string - - // NameFunc allows users to specify an alternative representation - // for references. It is called with the value passed to the top level - // method or function and the path to the entity being generated. - // If it returns an empty string the generator will expand the type - // in place and, if applicable, not generate a schema for that entity. - // - // Note: this only returns the final element of the /-separated - // reference. - NameFunc func(val cue.Value, path cue.Path) string - - // DescriptionFunc allows rewriting a description associated with a certain - // field. A typical implementation compiles the description from the - // comments obtains from the Doc method. No description field is added if - // the empty string is returned. - DescriptionFunc func(v cue.Value) string - - // SelfContained causes all non-expanded external references to be included - // in this document. - SelfContained bool - - // OpenAPI version to use. Supported as of v3.0.0. - Version string - - // FieldFilter defines a regular expression of all fields to omit from the - // output. It is only allowed to filter fields that add additional - // constraints. Fields that indicate basic types cannot be removed. It is - // an error for such fields to be excluded by this filter. - // Fields are qualified by their Object type. For instance, the - // minimum field of the schema object is qualified as Schema/minimum. - FieldFilter string - - // ExpandReferences replaces references with actual objects when generating - // OpenAPI Schema. It is an error for an CUE value to refer to itself - // if this option is used. - ExpandReferences bool -} diff --git a/encoding/crd/decode.go b/encoding/crd/decode.go deleted file mode 100644 index e029ca47640..00000000000 --- a/encoding/crd/decode.go +++ /dev/null @@ -1,40 +0,0 @@ -package crd - -import ( - "strings" - - "cuelang.org/go/cue" - "cuelang.org/go/cue/ast" - "cuelang.org/go/cue/errors" - "cuelang.org/go/cue/token" - "cuelang.org/go/encoding/openapi" - "cuelang.org/go/internal" -) - -// Extract converts CustomResourceDefinitions to an equivalent CUE representation. -// -// It currently converts entries in spec.versions[*].openApiV3. -func Extract(data cue.InstanceOrValue, c *Config) (*ast.File, error) { - return openapi.Extract(data, c.OpenAPICfg) -} - -const oapiSchemas = "#/components/schemas/" - -// rootDefs is the fallback for schemas that are not valid identifiers. -// TODO: find something more principled. -const rootDefs = "#SchemaMap" - -func openAPIMapping(pos token.Pos, a []string) ([]ast.Label, error) { - if len(a) != 3 || a[0] != "components" || a[1] != "schemas" { - return nil, errors.Newf(pos, - `openapi: reference must be of the form %q; found "#/%s"`, - oapiSchemas, strings.Join(a, "/")) - } - name := a[2] - if ast.IsValidIdent(name) && - name != rootDefs[1:] && - !internal.IsDefOrHidden(name) { - return []ast.Label{ast.NewIdent("#" + name)}, nil - } - return []ast.Label{ast.NewIdent(rootDefs), ast.NewString(name)}, nil -} diff --git a/encoding/crd/extract.go b/encoding/crd/extract.go new file mode 100644 index 00000000000..d6140199d33 --- /dev/null +++ b/encoding/crd/extract.go @@ -0,0 +1,392 @@ +package crd + +import ( + "fmt" + "path" + "strings" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/ast" + "cuelang.org/go/cue/ast/astutil" + "cuelang.org/go/cue/format" + "cuelang.org/go/cue/token" + "cuelang.org/go/encoding/openapi" + "cuelang.org/go/encoding/yaml" + "github.com/getkin/kin-openapi/openapi3" +) + +// Decoder generates CUE definitions from Kubernetes CRDs using the OpenAPI v3 spec. +type Decoder struct { + ctx *cue.Context + header string +} + +// NewDecoder creates an Importer for the given CUE context. +func NewDecoder(ctx *cue.Context, header string) *Decoder { + return &Decoder{ + ctx: ctx, + header: header, + } +} + +// Generate takes a multi-doc YAML containing Kubernetes CRDs and returns the CUE definitions +// generated from the OpenAPI spec. The resulting key value pairs, contain a unique identifier +// in the format `//` and the contents of the CUE definition. +func (imp *Decoder) Generate(crdData []byte) (map[string][]byte, error) { + result := make(map[string][]byte) + + crds, err := imp.fromYAML(crdData) + if err != nil { + return result, err + } + + for _, crd := range crds { + for _, crdVersion := range crd.Schemas { + def, err := format.Node(crdVersion.Schema.Syntax(cue.All(), cue.Docs(true))) + if err != nil { + return result, err + } + name := path.Join(crd.Props.Spec.Group, crd.Props.Spec.Names.Singular, crdVersion.Version) + result[name] = []byte(fmt.Sprintf("%s\n\npackage %s\n\n%s", imp.header, crdVersion.Version, string(def))) + } + } + + return result, nil +} + +// fromYAML converts a byte slice containing one or more YAML-encoded +// CustomResourceDefinitions into a slice of [IntermediateCRD]. +// +// This function preserves the ordering of schemas declared in the input YAML in +// the resulting [IntermediateCRD.Schemas] field. +func (imp *Decoder) fromYAML(b []byte) ([]*IntermediateCRD, error) { + + // The filename provided here is only used in error messages + yf, err := yaml.Extract("crd.yaml", b) + if err != nil { + return nil, fmt.Errorf("input is not valid yaml: %w", err) + } + crdv := imp.ctx.BuildFile(yf) + + var all []cue.Value + switch crdv.IncompleteKind() { + case cue.StructKind: + all = append(all, crdv) + case cue.ListKind: + iter, _ := crdv.List() + for iter.Next() { + all = append(all, iter.Value()) + } + default: + return nil, fmt.Errorf("input does not appear to be one or multiple CRDs: %s", crdv) + } + + ret := make([]*IntermediateCRD, 0, len(all)) + for _, crd := range all { + cc, err := convertCRD(crd) + if err != nil { + return nil, err + } + ret = append(ret, cc) + } + + return ret, nil +} + +// IntermediateCRD is an intermediate representation of CRD YAML. It contains the original CRD YAML input, +// a subset of useful naming-related fields, and an extracted list of the version schemas in the CRD, +// having been converted from OpenAPI to CUE. +type IntermediateCRD struct { + // The original unmodified CRD YAML, after conversion to a cue.Value. + Original cue.Value + Props struct { + Spec struct { + Group string `json:"group"` + Names struct { + Kind string `json:"kind"` + ListKind string `json:"listKind"` + Plural string `json:"plural"` + Singular string `json:"singular"` + } `json:"names"` + Scope string `json:"scope"` + } `json:"spec"` + } + + // All the schemas in the original CRD, converted to CUE representation. + Schemas []VersionedSchema +} + +// VersionedSchema is an intermediate form of a single versioned schema from a CRD +// (an element in `spec.versions`), converted to CUE. +type VersionedSchema struct { + // The contents of the `spec.versions[].name` + Version string + // The contents of `spec.versions[].schema.openAPIV3Schema`, after conversion of the OpenAPI + // schema to native CUE constraints. + Schema cue.Value +} + +func convertCRD(crd cue.Value) (*IntermediateCRD, error) { + cc := &IntermediateCRD{ + Schemas: make([]VersionedSchema, 0), + } + + err := crd.Decode(&cc.Props) + if err != nil { + return nil, fmt.Errorf("error decoding crd props into Go struct: %w", err) + } + // shorthand + kname := cc.Props.Spec.Names.Kind + + vlist := crd.LookupPath(cue.ParsePath("spec.versions")) + if !vlist.Exists() { + return nil, fmt.Errorf("crd versions list is absent") + } + iter, err := vlist.List() + if err != nil { + return nil, fmt.Errorf("crd versions field is not a list") + } + + ctx := crd.Context() + shell := ctx.CompileString(fmt.Sprintf(` + openapi: "3.0.0", + info: { + title: "dummy", + version: "1.0.0", + } + components: schemas: %s: _ + `, kname)) + schpath := cue.ParsePath("components.schemas." + kname) + defpath := cue.MakePath(cue.Def(kname)) + + // The CUE stdlib openapi encoder expects a whole openapi document, and then + // operates on schema elements defined within #/components/schema. Each + // versions[].schema.openAPIV3Schema within a CRD is ~equivalent to a single + // element under #/components/schema, as k8s does not allow CRD schemas to + // contain any kind of external references. + // + // So, for each schema.openAPIV3Schema, we wrap it in an openapi document + // structure, convert it to CUE, then appends it into the [IntermediateCRD.Schemas] slice. + var i int + for iter.Next() { + val := iter.Value() + ver, err := val.LookupPath(cue.ParsePath("name")).String() + if err != nil { + return nil, fmt.Errorf("unreachable? error getting version field for versions element at index %d: %w", i, err) + } + i++ + + doc := shell.FillPath(schpath, val.LookupPath(cue.ParsePath("schema.openAPIV3Schema"))) + of, err := openapi.Extract(doc, &openapi.Config{}) + if err != nil { + return nil, fmt.Errorf("could not convert schema for version %s to CUE: %w", ver, err) + } + + // first, extract and get the schema handle itself + extracted := ctx.BuildFile(of) + // then unify with our desired base constraints + nsConstraint := "!" + if cc.Props.Spec.Scope != "Namespaced" { + nsConstraint = "?" + } + sch := extracted.FillPath(defpath, ctx.CompileString(fmt.Sprintf(` + import "strings" + + apiVersion: "%s/%s" + kind: "%s" + + metadata!: { + name!: string & strings.MaxRunes(253) & strings.MinRunes(1) + namespace%s: string & strings.MaxRunes(63) & strings.MinRunes(1) + labels?: [string]: string + annotations?: [string]: string + } + `, cc.Props.Spec.Group, ver, kname, nsConstraint))) + + // now, go back to an AST because it's easier to manipulate references there + var schast *ast.File + switch x := sch.Syntax(cue.All(), cue.Docs(true)).(type) { + case *ast.File: + schast = x + case *ast.StructLit: + schast, _ = astutil.ToFile(x) + default: + panic("unreachable") + } + + // construct a map of all the paths that have x-kubernetes-embedded-resource: true defined + yodoc, err := yaml.Encode(doc) + if err != nil { + return nil, fmt.Errorf("error encoding intermediate openapi doc to yaml bytes: %w", err) + } + odoc, err := openapi3.NewLoader().LoadFromData(yodoc) + if err != nil { + return nil, fmt.Errorf("could not load openapi3 document for version %s: %w", ver, err) + } + + preserve := make(map[string]bool) + var rootosch *openapi3.Schema + if rref, has := odoc.Components.Schemas[kname]; !has { + return nil, fmt.Errorf("could not find root schema for version %s at expected path components.schemas.%s", ver, kname) + } else { + rootosch = rref.Value + } + + var walkfn func(path []cue.Selector, sch *openapi3.Schema) error + walkfn = func(path []cue.Selector, sch *openapi3.Schema) error { + _, has := sch.Extensions["x-kubernetes-preserve-unknown-fields"] + preserve[cue.MakePath(path...).String()] = has + for name, prop := range sch.Properties { + if err := walkfn(append(path, cue.Str(name)), prop.Value); err != nil { + return err + } + } + + return nil + } + + // Have to prepend with the defpath where the CUE CRD representation + // lives because the astutil walker to remove ellipses operates over the + // whole file, and therefore will be looking for full paths, extending + // all the way to the file root + err = walkfn(defpath.Selectors(), rootosch) + + // First pass of astutil.Apply to remove ellipses for fields not marked with x-kubernetes-embedded-resource: true + // Note that this implementation is only correct for CUE inputs that do not contain references. + // It is safe to use in this context because CRDs already have that invariant. + var stack []ast.Node + var pathstack []cue.Selector + astutil.Apply(schast, func(c astutil.Cursor) bool { + // Skip the root + if c.Parent() == nil { + return true + } + + switch x := c.Node().(type) { + case *ast.StructLit: + psel, pc := parentPath(c) + // Finding the parent-of-parent in this way is questionable. + // pathTo will hop up the tree a potentially large number of + // levels until it finds an *ast.Field or *ast.ListLit...but + // who knows what's between here and there? + _, ppc := parentPath(pc) + var i int + if ppc != nil { + for i = len(stack); i > 0 && stack[i-1] != ppc.Node(); i-- { + } + } + stack = append(stack[:i], pc.Node()) + pathstack = append(pathstack[:i], psel) + if !preserve[cue.MakePath(pathstack...).String()] { + newlist := make([]ast.Decl, 0, len(x.Elts)) + for _, elt := range x.Elts { + if _, is := elt.(*ast.Ellipsis); !is { + newlist = append(newlist, elt) + } + } + x.Elts = newlist + } + } + return true + }, nil) + + // walk over the AST and replace the spec and status fields with references to standalone defs + var specf, statusf *ast.Field + astutil.Apply(schast, func(cursor astutil.Cursor) bool { + switch x := cursor.Node().(type) { + case *ast.Field: + if str, _, err := ast.LabelName(x.Label); err == nil { + switch str { + // Grab pointers to the spec and status fields, and replace with ref + case "spec": + specf = new(ast.Field) + *specf = *x + specref := &ast.Field{ + Label: ast.NewIdent("spec"), + Value: ast.NewIdent("#" + kname + "Spec"), + } + specref.Constraint = token.NOT + astutil.CopyComments(specref, x) + cursor.Replace(specref) + return false + case "status": + //TODO: decide if status should be included + //statusf = new(ast.Field) + //*statusf = *x + cursor.Delete() + return false + case "metadata": + // Avoid walking other known subtrees + return false + case "info": + cursor.Delete() + } + } + } + return true + }, nil) + + if specf != nil { + specd := &ast.Field{ + Label: ast.NewIdent("#" + kname + "Spec"), + Value: specf.Value, + } + astutil.CopyComments(specd, specf) + schast.Decls = append(schast.Decls, specd) + } + + if statusf != nil { + statusd := &ast.Field{ + Label: ast.NewIdent("#" + kname + "Status"), + Value: statusf.Value, + } + astutil.CopyComments(statusd, statusf) + schast.Decls = append(schast.Decls, statusd) + } + + // Then build back to a cue.Value again for the return + cc.Schemas = append(cc.Schemas, VersionedSchema{ + Version: ver, + Schema: ctx.BuildFile(schast), + }) + } + return cc, nil +} + +// parentPath walks up the AST via Cursor.Parent() to find the parent AST node +// that is expected to be the anchor of a path element. +// +// Returns the cue.Selector that should navigate to the provided cursor's +// corresponding cue.Value, and the cursor of that parent element. +// +// Returns nil, nil if no such parent node can be found. +// +// Node types considered candidates for path anchors: +// - *ast.ListLit (index is the path) +// - *ast.Field (label is the path) +// +// If the there exceptions for the above two items, or the list should properly +// have more items, this func will be buggy +func parentPath(c astutil.Cursor) (cue.Selector, astutil.Cursor) { + p, prior := c.Parent(), c + for p != nil { + switch x := p.Node().(type) { + case *ast.Field: + lab, _, _ := ast.LabelName(x.Label) + if strings.HasPrefix(lab, "#") { + return cue.Def(lab), p + } + return cue.Str(lab), p + case *ast.ListLit: + for i, v := range x.Elts { + if prior.Node() == v { + return cue.Index(i), p + } + } + } + prior = p + p = p.Parent() + } + + return cue.Selector{}, nil +} diff --git a/encoding/crd/unmarshal.go b/encoding/crd/unmarshal.go new file mode 100644 index 00000000000..e03cdc3b429 --- /dev/null +++ b/encoding/crd/unmarshal.go @@ -0,0 +1,62 @@ +package crd + +import ( + "fmt" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/encoding/yaml" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" +) + +// Unmarshals a YAML file containing one or more CustomResourceDefinitions +// into a list of CRD objects +func Unmarshal(data []byte) ([]*v1.CustomResourceDefinition, error) { + // The filename provided here is only used in error messages + yf, err := yaml.Extract("crd.yaml", data) + if err != nil { + return nil, fmt.Errorf("input is not valid yaml: %w", err) + } + crdv := cuecontext.New().BuildFile(yf) + + var all []cue.Value + switch crdv.IncompleteKind() { + case cue.StructKind: + all = append(all, crdv) + case cue.ListKind: + iter, _ := crdv.List() + for iter.Next() { + all = append(all, iter.Value()) + } + default: + return nil, fmt.Errorf("input does not appear to be one or multiple CRDs: %s", crdv) + } + + // Make return value list + ret := make([]*v1.CustomResourceDefinition, 0, len(all)) + + // Create the "codec factory" that can decode CRDs + codecs := serializer.NewCodecFactory(scheme.Scheme, serializer.EnableStrict) + + // Iterate over each CRD + for _, cueval := range all { + // Encode the CUE value as YAML bytes + d, err := yaml.Encode(cueval) + if err != nil { + return ret, err + } + + // Decode into a v1.CustomResourceDefinition + obj := &v1.CustomResourceDefinition{} + if err := runtime.DecodeInto(codecs.UniversalDecoder(), d, obj); err != nil { + return ret, err + } + + ret = append(ret, obj) + } + + return ret, nil +} diff --git a/go.mod b/go.mod index cec52508dce..32128e3756e 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,11 @@ require ( cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 github.com/cockroachdb/apd/v3 v3.2.1 github.com/emicklei/proto v1.10.0 + github.com/getkin/kin-openapi v0.120.0 github.com/go-quicktest/qt v1.101.0 github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/google/uuid v1.2.0 + github.com/google/uuid v1.3.0 github.com/kr/pretty v0.3.1 github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de github.com/opencontainers/go-digest v1.0.0 @@ -20,17 +21,38 @@ require ( github.com/spf13/pflag v1.0.5 github.com/tetratelabs/wazero v1.0.2 golang.org/x/mod v0.13.0 - golang.org/x/net v0.16.0 + golang.org/x/net v0.17.0 golang.org/x/text v0.13.0 golang.org/x/tools v0.14.0 gopkg.in/yaml.v3 v3.0.1 + k8s.io/apiextensions-apiserver v0.28.3 + k8s.io/apimachinery v0.28.3 ) require ( + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.2.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/kr/text v0.2.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/sys v0.13.0 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 762770ec4d2..1d9531d91f5 100644 --- a/go.sum +++ b/go.sum @@ -4,33 +4,76 @@ github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEa github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= +github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIsgmHpEOGbUs6VtHBXRR1OHevnj7hLx9ZcdNGW4= github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -41,23 +84,80 @@ github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/tetratelabs/wazero v1.0.2 h1:lpwL5zczFHk2mxKur98035Gig+Z3vd9JURk6lUdZxXY= github.com/tetratelabs/wazero v1.0.2/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= -golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= +k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= +k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= +k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go index 162ab806963..7c61ec99167 100644 --- a/internal/encoding/encoding.go +++ b/internal/encoding/encoding.go @@ -33,7 +33,6 @@ import ( "cuelang.org/go/cue/literal" "cuelang.org/go/cue/parser" "cuelang.org/go/cue/token" - "cuelang.org/go/encoding/crd" "cuelang.org/go/encoding/json" "cuelang.org/go/encoding/jsonschema" "cuelang.org/go/encoding/openapi" @@ -225,9 +224,6 @@ func NewDecoder(f *build.File, cfg *Config) *Decoder { case build.ProtobufJSON: i.interpretation = build.ProtobufJSON i.rewriteFunc = protobufJSONFunc(cfg, f) - case build.CustomResourceDefinition: - i.interpretation = build.CustomResourceDefinition - i.rewriteFunc = customResourceDefinitionFunc(cfg, f) default: i.err = fmt.Errorf("unsupported interpretation %q", f.Interpretation) } @@ -328,15 +324,14 @@ func protobufJSONFunc(cfg *Config, file *build.File) rewriteFunc { } } -func customResourceDefinitionFunc(c *Config, f *build.File) interpretFunc { - cfg := &crd.Config{PkgName: c.PkgName} - return func(i *cue.Instance) (file *ast.File, id string, err error) { - file, err = crd.Extract(i, cfg) - // TODO: simplify currently erases file line info. Reintroduce after fix. - // file, err = simplify(file, err) - return file, "", err - } -} +// func customResourceDefinitionFunc(c *Config, f *build.File) interpretFunc { +// return func(i *cue.Instance) (file *ast.File, id string, err error) { +// file, err = crd.Extract(i, cfg) +// // TODO: simplify currently erases file line info. Reintroduce after fix. +// // file, err = simplify(file, err) +// return file, "", err +// } +// } func reader(f *build.File, stdin io.Reader) (io.ReadCloser, error) { switch s := f.Source.(type) { From a9ee74e468b87c45d82f64dcd3333a4fe418787d Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Mon, 13 Nov 2023 17:56:02 -0500 Subject: [PATCH 03/20] exploring attributes --- encoding/crd/extract.go | 68 +++++++++++++++++++++++++++++++++++++-- encoding/crd/unmarshal.go | 49 ++++++++++++++++++++-------- 2 files changed, 101 insertions(+), 16 deletions(-) diff --git a/encoding/crd/extract.go b/encoding/crd/extract.go index d6140199d33..61cfc034e58 100644 --- a/encoding/crd/extract.go +++ b/encoding/crd/extract.go @@ -8,11 +8,13 @@ import ( "cuelang.org/go/cue" "cuelang.org/go/cue/ast" "cuelang.org/go/cue/ast/astutil" + "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/format" "cuelang.org/go/cue/token" "cuelang.org/go/encoding/openapi" "cuelang.org/go/encoding/yaml" "github.com/getkin/kin-openapi/openapi3" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) // Decoder generates CUE definitions from Kubernetes CRDs using the OpenAPI v3 spec. @@ -98,8 +100,9 @@ func (imp *Decoder) fromYAML(b []byte) ([]*IntermediateCRD, error) { // having been converted from OpenAPI to CUE. type IntermediateCRD struct { // The original unmodified CRD YAML, after conversion to a cue.Value. - Original cue.Value - Props struct { + Original cue.Value + Props *v1.CustomResourceDefinition + internalProps struct { Spec struct { Group string `json:"group"` Names struct { @@ -131,7 +134,13 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { Schemas: make([]VersionedSchema, 0), } - err := crd.Decode(&cc.Props) + var err error + cc.Props, err = UnmarshalOne(crd) + if err != nil { + return nil, fmt.Errorf("error decoding crd props into Go struct: %w", err) + } + + err = crd.Decode(&cc.internalProps) if err != nil { return nil, fmt.Errorf("error decoding crd props into Go struct: %w", err) } @@ -245,11 +254,19 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { return nil } + extensions, err := exts(ctx, defpath, *cc.Props.Spec.Versions[0].Schema.OpenAPIV3Schema) + if err != nil { + return nil, err + } + // Have to prepend with the defpath where the CUE CRD representation // lives because the astutil walker to remove ellipses operates over the // whole file, and therefore will be looking for full paths, extending // all the way to the file root err = walkfn(defpath.Selectors(), rootosch) + if err != nil { + return nil, err + } // First pass of astutil.Apply to remove ellipses for fields not marked with x-kubernetes-embedded-resource: true // Note that this implementation is only correct for CUE inputs that do not contain references. @@ -390,3 +407,48 @@ func parentPath(c astutil.Cursor) (cue.Selector, astutil.Cursor) { return cue.Selector{}, nil } + +// exts preserves k8s OpenAPI extensions as attributes +func exts(ctx *cue.Context, path cue.Path, props v1.JSONSchemaProps) (cue.Value, error) { + extensions := cue.Value{} + + if props.XPreserveUnknownFields != nil { + extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-preserve-unknown-fields"=%t)`, *props.XPreserveUnknownFields))) + } + + if props.XEmbeddedResource { + extensions = extensions.FillPath(path, ctx.CompileString(`@k8s("x-kubernetes-embedded-resource"=true)`)) + } + + if props.XIntOrString { + extensions = extensions.FillPath(path, ctx.CompileString(`@k8s("x-kubernetes-int-or-string"=true)`)) + } + + if len(props.XListMapKeys) > 0 { + extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-list-map-keys"=["%s"])`, strings.Join(props.XListMapKeys, `", "`)))) + } + + if props.XListType != nil { + extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-list-type"="%s")`, *props.XListType))) + } + + if props.XMapType != nil { + extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-map-type"="%s")`, *props.XMapType))) + } + + if len(props.XValidations) > 0 { + extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-validations"="%s")`, cuecontext.New().Encode(props.XValidations)))) + } + + for name, subProp := range props.Properties { + subExtensions, err := exts(ctx, cue.MakePath(cue.Str(name)), subProp) + if err != nil { + return extensions, err + } + + // Merge subextensions at this level + extensions = extensions.FillPath(path, subExtensions) + } + + return extensions, nil +} diff --git a/encoding/crd/unmarshal.go b/encoding/crd/unmarshal.go index e03cdc3b429..8b49931c7d5 100644 --- a/encoding/crd/unmarshal.go +++ b/encoding/crd/unmarshal.go @@ -2,6 +2,7 @@ package crd import ( "fmt" + "os" "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" @@ -12,16 +13,30 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" ) +var ( + // The "codec factory" that can decode CRDs + codecs = serializer.NewCodecFactory(scheme.Scheme, serializer.EnableStrict) +) + // Unmarshals a YAML file containing one or more CustomResourceDefinitions // into a list of CRD objects -func Unmarshal(data []byte) ([]*v1.CustomResourceDefinition, error) { +func UnmarshalFile(filename string) ([]*v1.CustomResourceDefinition, error) { + data, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + // The filename provided here is only used in error messages - yf, err := yaml.Extract("crd.yaml", data) + yf, err := yaml.Extract(filename, data) if err != nil { return nil, fmt.Errorf("input is not valid yaml: %w", err) } crdv := cuecontext.New().BuildFile(yf) + return Unmarshal(crdv) +} + +func Unmarshal(crdv cue.Value) ([]*v1.CustomResourceDefinition, error) { var all []cue.Value switch crdv.IncompleteKind() { case cue.StructKind: @@ -38,25 +53,33 @@ func Unmarshal(data []byte) ([]*v1.CustomResourceDefinition, error) { // Make return value list ret := make([]*v1.CustomResourceDefinition, 0, len(all)) - // Create the "codec factory" that can decode CRDs - codecs := serializer.NewCodecFactory(scheme.Scheme, serializer.EnableStrict) - // Iterate over each CRD for _, cueval := range all { - // Encode the CUE value as YAML bytes - d, err := yaml.Encode(cueval) + obj, err := UnmarshalOne(cueval) if err != nil { return ret, err } - // Decode into a v1.CustomResourceDefinition - obj := &v1.CustomResourceDefinition{} - if err := runtime.DecodeInto(codecs.UniversalDecoder(), d, obj); err != nil { - return ret, err - } - ret = append(ret, obj) } return ret, nil } + +// Unmarshals YAML data for a single containing one or more CustomResourceDefinitions +// into a list of CRD objects +func UnmarshalOne(val cue.Value) (*v1.CustomResourceDefinition, error) { + // Encode the CUE value as YAML bytes + d, err := yaml.Encode(val) + if err != nil { + return nil, err + } + + // Decode into a v1.CustomResourceDefinition + obj := &v1.CustomResourceDefinition{} + if err := runtime.DecodeInto(codecs.UniversalDecoder(), d, obj); err != nil { + return nil, err + } + + return obj, nil +} From 5f0237cfb06737719a7be001cf33e50504b244c7 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Mon, 13 Nov 2023 20:46:04 -0500 Subject: [PATCH 04/20] wire up to the command for testing the experiment --- cmd/cue/cmd/get_crd.go | 32 +++++++++++++++++++++++--------- encoding/crd/extract.go | 21 +++++++-------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/cmd/cue/cmd/get_crd.go b/cmd/cue/cmd/get_crd.go index 86541aab4c7..a1e2840d1c3 100644 --- a/cmd/cue/cmd/get_crd.go +++ b/cmd/cue/cmd/get_crd.go @@ -15,8 +15,12 @@ package cmd import ( - "cuelang.org/go/cue/build" - "cuelang.org/go/cue/load" + "os" + "path/filepath" + "strings" + + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/encoding/crd" "github.com/spf13/cobra" ) @@ -50,24 +54,34 @@ CustomResourceDefinitions are converted to cue structs adhering to the following - The @x-kubernetes-validation attribute is added if the field utilizes the "x-kubernetes-validation" extension. `, - RunE: mkRunE(c, extract), + RunE: mkRunE(c, runGetCRD), } return cmd } func runGetCRD(cmd *Command, args []string) error { - c := &config{ - fileFilter: `\.(json|yaml|yml|jsonl|ldjson)$`, - interpretation: build.CustomResourceDefinition, - encoding: "yaml", - loadCfg: &load.Config{DataFiles: true}, + decoder := crd.NewDecoder(cuecontext.New(), "cue get crd "+strings.Join(args, " ")) + + data, err := os.ReadFile(args[0]) + if err != nil { + return err } - p, err := newBuildPlan(cmd, c) + defs, err := decoder.Generate(data) if err != nil { return err } + for path, crd := range defs { + if err = os.MkdirAll(path, 0644); err != nil { + return err + } + + if err = os.WriteFile(filepath.Join(path, "types_gen.cue"), crd, 0644); err != nil { + return err + } + } + return nil } diff --git a/encoding/crd/extract.go b/encoding/crd/extract.go index 61cfc034e58..d7594f0a4a7 100644 --- a/encoding/crd/extract.go +++ b/encoding/crd/extract.go @@ -212,6 +212,9 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { } `, cc.Props.Spec.Group, ver, kname, nsConstraint))) + // Add x-kubernetes-* OpenAPI extensions as attributes + sch = sch.FillPath(defpath, exts(ctx, defpath, *cc.Props.Spec.Versions[i].Schema.OpenAPIV3Schema)) + // now, go back to an AST because it's easier to manipulate references there var schast *ast.File switch x := sch.Syntax(cue.All(), cue.Docs(true)).(type) { @@ -254,11 +257,6 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { return nil } - extensions, err := exts(ctx, defpath, *cc.Props.Spec.Versions[0].Schema.OpenAPIV3Schema) - if err != nil { - return nil, err - } - // Have to prepend with the defpath where the CUE CRD representation // lives because the astutil walker to remove ellipses operates over the // whole file, and therefore will be looking for full paths, extending @@ -409,7 +407,7 @@ func parentPath(c astutil.Cursor) (cue.Selector, astutil.Cursor) { } // exts preserves k8s OpenAPI extensions as attributes -func exts(ctx *cue.Context, path cue.Path, props v1.JSONSchemaProps) (cue.Value, error) { +func exts(ctx *cue.Context, path cue.Path, props v1.JSONSchemaProps) cue.Value { extensions := cue.Value{} if props.XPreserveUnknownFields != nil { @@ -441,14 +439,9 @@ func exts(ctx *cue.Context, path cue.Path, props v1.JSONSchemaProps) (cue.Value, } for name, subProp := range props.Properties { - subExtensions, err := exts(ctx, cue.MakePath(cue.Str(name)), subProp) - if err != nil { - return extensions, err - } - - // Merge subextensions at this level - extensions = extensions.FillPath(path, subExtensions) + // Recursively add subextensions for each property + extensions = extensions.FillPath(path, exts(ctx, cue.MakePath(cue.Str(name)), subProp)) } - return extensions, nil + return extensions } From 5d2b1ee07520919645c25e30a6d8584ead00373a Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Mon, 13 Nov 2023 23:24:47 -0500 Subject: [PATCH 05/20] get tests running --- cmd/cue/cmd/get.go | 2 +- cmd/cue/cmd/get_crd.go | 22 +++-- encoding/crd/extract.go | 193 ++++++++++++++++++++++++++++---------- encoding/crd/unmarshal.go | 5 +- 4 files changed, 164 insertions(+), 58 deletions(-) diff --git a/cmd/cue/cmd/get.go b/cmd/cue/cmd/get.go index e562f78d024..b4bfef0c130 100644 --- a/cmd/cue/cmd/get.go +++ b/cmd/cue/cmd/get.go @@ -47,6 +47,6 @@ per language and are documented in the respective subcommands. return nil }), } - cmd.AddCommand(newGoCmd(c)) + cmd.AddCommand(newGoCmd(c), newCrdCmd(c)) return cmd } diff --git a/cmd/cue/cmd/get_crd.go b/cmd/cue/cmd/get_crd.go index a1e2840d1c3..ebdb80ca47e 100644 --- a/cmd/cue/cmd/get_crd.go +++ b/cmd/cue/cmd/get_crd.go @@ -16,10 +16,10 @@ package cmd import ( "os" - "path/filepath" + "path" + "sort" "strings" - "cuelang.org/go/cue/cuecontext" "cuelang.org/go/encoding/crd" "github.com/spf13/cobra" ) @@ -61,24 +61,32 @@ CustomResourceDefinitions are converted to cue structs adhering to the following } func runGetCRD(cmd *Command, args []string) error { - decoder := crd.NewDecoder(cuecontext.New(), "cue get crd "+strings.Join(args, " ")) + decoder := crd.NewDecoder(cmd.ctx, "// cue get crd "+strings.Join(args, " ")) data, err := os.ReadFile(args[0]) if err != nil { return err } - defs, err := decoder.Generate(data) + crds, err := decoder.Generate(data) if err != nil { return err } - for path, crd := range defs { - if err = os.MkdirAll(path, 0644); err != nil { + // Sort the resulting definitions based on file names. + keys := make([]string, 0, len(crds)) + for k := range crds { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + dstDir := path.Join("cue.mod", "gen", k) + if err := os.MkdirAll(dstDir, os.ModePerm); err != nil { return err } - if err = os.WriteFile(filepath.Join(path, "types_gen.cue"), crd, 0644); err != nil { + if err := os.WriteFile(path.Join(dstDir, "types_gen.cue"), crds[k], 0644); err != nil { return err } } diff --git a/encoding/crd/extract.go b/encoding/crd/extract.go index d7594f0a4a7..eb0e19924fc 100644 --- a/encoding/crd/extract.go +++ b/encoding/crd/extract.go @@ -1,6 +1,7 @@ package crd import ( + "encoding/json" "fmt" "path" "strings" @@ -8,12 +9,10 @@ import ( "cuelang.org/go/cue" "cuelang.org/go/cue/ast" "cuelang.org/go/cue/ast/astutil" - "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/format" "cuelang.org/go/cue/token" "cuelang.org/go/encoding/openapi" "cuelang.org/go/encoding/yaml" - "github.com/getkin/kin-openapi/openapi3" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) @@ -212,9 +211,6 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { } `, cc.Props.Spec.Group, ver, kname, nsConstraint))) - // Add x-kubernetes-* OpenAPI extensions as attributes - sch = sch.FillPath(defpath, exts(ctx, defpath, *cc.Props.Spec.Versions[i].Schema.OpenAPIV3Schema)) - // now, go back to an AST because it's easier to manipulate references there var schast *ast.File switch x := sch.Syntax(cue.All(), cue.Docs(true)).(type) { @@ -226,30 +222,21 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { panic("unreachable") } - // construct a map of all the paths that have x-kubernetes-embedded-resource: true defined - yodoc, err := yaml.Encode(doc) - if err != nil { - return nil, fmt.Errorf("error encoding intermediate openapi doc to yaml bytes: %w", err) - } - odoc, err := openapi3.NewLoader().LoadFromData(yodoc) - if err != nil { - return nil, fmt.Errorf("could not load openapi3 document for version %s: %w", ver, err) - } + // rootosch is a shortcut to this version's schema + rootosch := *cc.Props.Spec.Versions[i-1].Schema.OpenAPIV3Schema - preserve := make(map[string]bool) - var rootosch *openapi3.Schema - if rref, has := odoc.Components.Schemas[kname]; !has { - return nil, fmt.Errorf("could not find root schema for version %s at expected path components.schemas.%s", ver, kname) - } else { - rootosch = rref.Value - } + // construct a map of all paths using x-kubernetes-* OpenAPI extensions + extAttrs := xKubernetesAttributes(defpath.Selectors(), rootosch) - var walkfn func(path []cue.Selector, sch *openapi3.Schema) error - walkfn = func(path []cue.Selector, sch *openapi3.Schema) error { - _, has := sch.Extensions["x-kubernetes-preserve-unknown-fields"] - preserve[cue.MakePath(path...).String()] = has - for name, prop := range sch.Properties { - if err := walkfn(append(path, cue.Str(name)), prop.Value); err != nil { + // construct a map of all the paths that have x-kubernetes-embedded-resource: true defined + preserve := make(map[string]bool) + var walkfn func(path []cue.Selector, sch v1.JSONSchemaProps) error + walkfn = func(path []cue.Selector, sch v1.JSONSchemaProps) error { + if sch.XPreserveUnknownFields != nil { + preserve[cue.MakePath(path...).String()] = *sch.XPreserveUnknownFields + } + for name, nextProp := range sch.Properties { + if err := walkfn(append(path, cue.Str(name)), nextProp); err != nil { return err } } @@ -271,6 +258,7 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { // It is safe to use in this context because CRDs already have that invariant. var stack []ast.Node var pathstack []cue.Selector + astutil.Apply(schast, func(c astutil.Cursor) bool { // Skip the root if c.Parent() == nil { @@ -292,7 +280,9 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { } stack = append(stack[:i], pc.Node()) pathstack = append(pathstack[:i], psel) - if !preserve[cue.MakePath(pathstack...).String()] { + pathKey := cue.MakePath(pathstack...).String() + + if !preserve[pathKey] { newlist := make([]ast.Decl, 0, len(x.Elts)) for _, elt := range x.Elts { if _, is := elt.(*ast.Ellipsis); !is { @@ -301,6 +291,15 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { } x.Elts = newlist } + + if attr, ok := extAttrs[pathKey]; ok { + x.Elts = append(x.Elts, &attr) + } + // TODO: how do I get the path for this case? + // case *ast.Field: + // if attr, ok := extAttrs[pathKey]; ok { + // x.Attrs = append(x.Attrs, &attr) + // } } return true }, nil) @@ -406,41 +405,141 @@ func parentPath(c astutil.Cursor) (cue.Selector, astutil.Cursor) { return cue.Selector{}, nil } +type XExtensionAttr string + +const ( + XPreserveUnknownFields XExtensionAttr = "preserveUnknownFields" + XEmbeddedResource XExtensionAttr = "embeddedResource" + XIntOrString XExtensionAttr = "intOrString" + XListMapKeys XExtensionAttr = "listMapKeys" + XListType XExtensionAttr = "listType" + XMapType XExtensionAttr = "mapType" + XValidations XExtensionAttr = "validations" + // XPreserveUnknownFields XExtensionAttr = "x-kubernetes-preserve-unknown-fields" + // XEmbeddedResource XExtensionAttr = "x-kubernetes-embedded-resource" + // XIntOrString XExtensionAttr = "x-kubernetes-int-or-string" + // XListMapKeys XExtensionAttr = "x-kubernetes-list-map-keys" + // XListType XExtensionAttr = "x-kubernetes-list-type" + // XMapType XExtensionAttr = "x-kubernetes-map-type" + // XValidations XExtensionAttr = "x-kubernetes-validations" +) + // exts preserves k8s OpenAPI extensions as attributes -func exts(ctx *cue.Context, path cue.Path, props v1.JSONSchemaProps) cue.Value { - extensions := cue.Value{} +func exts(path []cue.Selector, prop v1.JSONSchemaProps) map[string]map[XExtensionAttr]any { + extensions := map[string]map[XExtensionAttr]any{} + + pathKey := cue.MakePath(path...).String() + + addExt := func(name XExtensionAttr, value any) { + if extensions[pathKey] == nil { + extensions[pathKey] = map[XExtensionAttr]any{} + } + + extensions[pathKey][name] = value + } + + if prop.XPreserveUnknownFields != nil { + addExt(XPreserveUnknownFields, *prop.XPreserveUnknownFields) + // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-preserve-unknown-fields"=%t)`, *prop.XPreserveUnknownFields) + } + + if prop.XEmbeddedResource { + addExt(XEmbeddedResource, prop.XEmbeddedResource) + // extensions[pathKey] = `@crd("x-kubernetes-embedded-resource"=true)` + } - if props.XPreserveUnknownFields != nil { - extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-preserve-unknown-fields"=%t)`, *props.XPreserveUnknownFields))) + if prop.XIntOrString { + addExt(XIntOrString, prop.XIntOrString) + // extensions[pathKey] = `@crd("x-kubernetes-int-or-string"=true)` } - if props.XEmbeddedResource { - extensions = extensions.FillPath(path, ctx.CompileString(`@k8s("x-kubernetes-embedded-resource"=true)`)) + if len(prop.XListMapKeys) > 0 { + addExt(XListMapKeys, fmt.Sprintf(`@crd("x-kubernetes-list-map-keys"=["%s"])`, strings.Join(prop.XListMapKeys, `", "`))) + // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-list-map-keys"=["%s"])`, strings.Join(prop.XListMapKeys, `", "`)) } - if props.XIntOrString { - extensions = extensions.FillPath(path, ctx.CompileString(`@k8s("x-kubernetes-int-or-string"=true)`)) + if prop.XListType != nil { + addExt(XListType, *prop.XListType) + // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-list-type"="%s")`, *prop.XListType) } - if len(props.XListMapKeys) > 0 { - extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-list-map-keys"=["%s"])`, strings.Join(props.XListMapKeys, `", "`)))) + if prop.XMapType != nil { + addExt(XMapType, *prop.XMapType) + // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-map-type"="%s")`, *prop.XMapType) } - if props.XListType != nil { - extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-list-type"="%s")`, *props.XListType))) + if len(prop.XValidations) > 0 { + vals, err := json.Marshal(prop.XValidations) + if err != nil { + panic(err) + } + addExt(XValidations, string(vals)) + // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-validations"="%s")`, string(vals)) + } + + for name, nextProp := range prop.Properties { + // Recursively add subextensions for each property\ + subExts := exts(append(path, cue.Str(name)), nextProp) + for key, val := range subExts { + extensions[key] = val + } + } + + return extensions +} + +// Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them +func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) map[string]ast.Attribute { + extensions := map[string]ast.Attribute{} + + pathKey := cue.MakePath(path...).String() + + attrBody := []string{} + addAttr := func(key XExtensionAttr, val string) { + attrBody = append(attrBody, fmt.Sprintf("%s=%s", key, val)) + } + + if prop.XPreserveUnknownFields != nil { + addAttr(XPreserveUnknownFields, fmt.Sprintf("%t", *prop.XPreserveUnknownFields)) + } + + if prop.XEmbeddedResource { + addAttr(XEmbeddedResource, fmt.Sprintf("%t", *prop.XPreserveUnknownFields)) + } + + if prop.XIntOrString { + addAttr(XIntOrString, fmt.Sprintf("%t", *prop.XPreserveUnknownFields)) + } + + if len(prop.XListMapKeys) > 0 { + addAttr(XListMapKeys, fmt.Sprintf(`["%s"])`, strings.Join(prop.XListMapKeys, `", "`))) } - if props.XMapType != nil { - extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-map-type"="%s")`, *props.XMapType))) + if prop.XListType != nil { + addAttr(XListType, fmt.Sprintf("%q", *prop.XListType)) } - if len(props.XValidations) > 0 { - extensions = extensions.FillPath(path, ctx.CompileString(fmt.Sprintf(`@k8s("x-kubernetes-validations"="%s")`, cuecontext.New().Encode(props.XValidations)))) + if prop.XMapType != nil { + addAttr(XMapType, fmt.Sprintf("%q", *prop.XMapType)) } - for name, subProp := range props.Properties { - // Recursively add subextensions for each property - extensions = extensions.FillPath(path, exts(ctx, cue.MakePath(cue.Str(name)), subProp)) + if len(prop.XValidations) > 0 { + vals, err := json.Marshal(prop.XValidations) + if err != nil { + panic(err) + } + addAttr(XValidations, `"`+string(vals)+`"`) + } + + attrText := fmt.Sprintf("@%s(%s)", "xKubernetes", strings.Join(attrBody, ", ")) + extensions[pathKey] = ast.Attribute{Text: attrText} + + for name, nextProp := range prop.Properties { + // Recursively add subextensions for each property\ + subExts := xKubernetesAttributes(append(path, cue.Str(name)), nextProp) + for key, val := range subExts { + extensions[key] = val + } } return extensions diff --git a/encoding/crd/unmarshal.go b/encoding/crd/unmarshal.go index 8b49931c7d5..72383668a18 100644 --- a/encoding/crd/unmarshal.go +++ b/encoding/crd/unmarshal.go @@ -5,7 +5,6 @@ import ( "os" "cuelang.org/go/cue" - "cuelang.org/go/cue/cuecontext" "cuelang.org/go/encoding/yaml" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" @@ -20,7 +19,7 @@ var ( // Unmarshals a YAML file containing one or more CustomResourceDefinitions // into a list of CRD objects -func UnmarshalFile(filename string) ([]*v1.CustomResourceDefinition, error) { +func UnmarshalFile(ctx *cue.Context, filename string) ([]*v1.CustomResourceDefinition, error) { data, err := os.ReadFile(filename) if err != nil { return nil, err @@ -31,7 +30,7 @@ func UnmarshalFile(filename string) ([]*v1.CustomResourceDefinition, error) { if err != nil { return nil, fmt.Errorf("input is not valid yaml: %w", err) } - crdv := cuecontext.New().BuildFile(yf) + crdv := ctx.BuildFile(yf) return Unmarshal(crdv) } From f911a6aee4315cad4f81a9e1f759efc7e8a42eee Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Mon, 13 Nov 2023 23:48:09 -0500 Subject: [PATCH 06/20] fmt --- encoding/crd/extract.go | 98 ++++------------------------------------- 1 file changed, 9 insertions(+), 89 deletions(-) diff --git a/encoding/crd/extract.go b/encoding/crd/extract.go index eb0e19924fc..d322206f483 100644 --- a/encoding/crd/extract.go +++ b/encoding/crd/extract.go @@ -99,20 +99,10 @@ func (imp *Decoder) fromYAML(b []byte) ([]*IntermediateCRD, error) { // having been converted from OpenAPI to CUE. type IntermediateCRD struct { // The original unmodified CRD YAML, after conversion to a cue.Value. - Original cue.Value - Props *v1.CustomResourceDefinition - internalProps struct { - Spec struct { - Group string `json:"group"` - Names struct { - Kind string `json:"kind"` - ListKind string `json:"listKind"` - Plural string `json:"plural"` - Singular string `json:"singular"` - } `json:"names"` - Scope string `json:"scope"` - } `json:"spec"` - } + Original cue.Value + + // Object form of CRD, decoded by k8s decoder + Props *v1.CustomResourceDefinition // All the schemas in the original CRD, converted to CUE representation. Schemas []VersionedSchema @@ -139,10 +129,6 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { return nil, fmt.Errorf("error decoding crd props into Go struct: %w", err) } - err = crd.Decode(&cc.internalProps) - if err != nil { - return nil, fmt.Errorf("error decoding crd props into Go struct: %w", err) - } // shorthand kname := cc.Props.Spec.Names.Kind @@ -259,6 +245,11 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { var stack []ast.Node var pathstack []cue.Selector + ast.Walk(schast, func(n ast.Node) bool { + fmt.Printf("n: %v\n", n) + return true + }, nil) + astutil.Apply(schast, func(c astutil.Cursor) bool { // Skip the root if c.Parent() == nil { @@ -415,79 +406,8 @@ const ( XListType XExtensionAttr = "listType" XMapType XExtensionAttr = "mapType" XValidations XExtensionAttr = "validations" - // XPreserveUnknownFields XExtensionAttr = "x-kubernetes-preserve-unknown-fields" - // XEmbeddedResource XExtensionAttr = "x-kubernetes-embedded-resource" - // XIntOrString XExtensionAttr = "x-kubernetes-int-or-string" - // XListMapKeys XExtensionAttr = "x-kubernetes-list-map-keys" - // XListType XExtensionAttr = "x-kubernetes-list-type" - // XMapType XExtensionAttr = "x-kubernetes-map-type" - // XValidations XExtensionAttr = "x-kubernetes-validations" ) -// exts preserves k8s OpenAPI extensions as attributes -func exts(path []cue.Selector, prop v1.JSONSchemaProps) map[string]map[XExtensionAttr]any { - extensions := map[string]map[XExtensionAttr]any{} - - pathKey := cue.MakePath(path...).String() - - addExt := func(name XExtensionAttr, value any) { - if extensions[pathKey] == nil { - extensions[pathKey] = map[XExtensionAttr]any{} - } - - extensions[pathKey][name] = value - } - - if prop.XPreserveUnknownFields != nil { - addExt(XPreserveUnknownFields, *prop.XPreserveUnknownFields) - // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-preserve-unknown-fields"=%t)`, *prop.XPreserveUnknownFields) - } - - if prop.XEmbeddedResource { - addExt(XEmbeddedResource, prop.XEmbeddedResource) - // extensions[pathKey] = `@crd("x-kubernetes-embedded-resource"=true)` - } - - if prop.XIntOrString { - addExt(XIntOrString, prop.XIntOrString) - // extensions[pathKey] = `@crd("x-kubernetes-int-or-string"=true)` - } - - if len(prop.XListMapKeys) > 0 { - addExt(XListMapKeys, fmt.Sprintf(`@crd("x-kubernetes-list-map-keys"=["%s"])`, strings.Join(prop.XListMapKeys, `", "`))) - // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-list-map-keys"=["%s"])`, strings.Join(prop.XListMapKeys, `", "`)) - } - - if prop.XListType != nil { - addExt(XListType, *prop.XListType) - // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-list-type"="%s")`, *prop.XListType) - } - - if prop.XMapType != nil { - addExt(XMapType, *prop.XMapType) - // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-map-type"="%s")`, *prop.XMapType) - } - - if len(prop.XValidations) > 0 { - vals, err := json.Marshal(prop.XValidations) - if err != nil { - panic(err) - } - addExt(XValidations, string(vals)) - // extensions[pathKey] = fmt.Sprintf(`@crd("x-kubernetes-validations"="%s")`, string(vals)) - } - - for name, nextProp := range prop.Properties { - // Recursively add subextensions for each property\ - subExts := exts(append(path, cue.Str(name)), nextProp) - for key, val := range subExts { - extensions[key] = val - } - } - - return extensions -} - // Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) map[string]ast.Attribute { extensions := map[string]ast.Attribute{} From 612dd88ddf76a6fe621fbb18aa4f3375013a1871 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Tue, 14 Nov 2023 18:23:27 -0500 Subject: [PATCH 07/20] add attributes for crd oapi extensions --- encoding/crd/extract.go | 106 ++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/encoding/crd/extract.go b/encoding/crd/extract.go index d322206f483..379aeb46c89 100644 --- a/encoding/crd/extract.go +++ b/encoding/crd/extract.go @@ -136,10 +136,6 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { if !vlist.Exists() { return nil, fmt.Errorf("crd versions list is absent") } - iter, err := vlist.List() - if err != nil { - return nil, fmt.Errorf("crd versions field is not a list") - } ctx := crd.Context() shell := ctx.CompileString(fmt.Sprintf(` @@ -161,16 +157,15 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { // // So, for each schema.openAPIV3Schema, we wrap it in an openapi document // structure, convert it to CUE, then appends it into the [IntermediateCRD.Schemas] slice. - var i int - for iter.Next() { - val := iter.Value() - ver, err := val.LookupPath(cue.ParsePath("name")).String() - if err != nil { - return nil, fmt.Errorf("unreachable? error getting version field for versions element at index %d: %w", i, err) + for i, crdVersion := range cc.Props.Spec.Versions { + ver := crdVersion.Name + if crdVersion.Schema == nil || crdVersion.Schema.OpenAPIV3Schema == nil { + continue } - i++ - doc := shell.FillPath(schpath, val.LookupPath(cue.ParsePath("schema.openAPIV3Schema"))) + rootosch := *crdVersion.Schema.OpenAPIV3Schema + + doc := shell.FillPath(schpath, crd.LookupPath(cue.ParsePath(fmt.Sprintf("spec.versions[%d].schema.openAPIV3Schema", i)))) of, err := openapi.Extract(doc, &openapi.Config{}) if err != nil { return nil, fmt.Errorf("could not convert schema for version %s to CUE: %w", ver, err) @@ -178,6 +173,20 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { // first, extract and get the schema handle itself extracted := ctx.BuildFile(of) + + // Add attributes for k8s oapi extensions + // construct a map of all paths using x-kubernetes-* OpenAPI extensions + for _, extAttr := range xKubernetesAttributes(defpath.Selectors(), rootosch) { + extendedVal := extracted.LookupPath(cue.MakePath(extAttr.path...)) + node := extendedVal.Source() + switch x := node.(type) { + case *ast.StructLit: + x.Elts = append(x.Elts, extAttr.attr) + case *ast.Field: + x.Attrs = append(x.Attrs, extAttr.attr) + } + } + // then unify with our desired base constraints nsConstraint := "!" if cc.Props.Spec.Scope != "Namespaced" { @@ -197,6 +206,11 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { } `, cc.Props.Spec.Group, ver, kname, nsConstraint))) + // for path, attr := range extAttrs { + // cue.MakePath(cue) + // sch.LookupPath(cue.MakePath(cue.Str(path))) + // } + // now, go back to an AST because it's easier to manipulate references there var schast *ast.File switch x := sch.Syntax(cue.All(), cue.Docs(true)).(type) { @@ -208,12 +222,6 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { panic("unreachable") } - // rootosch is a shortcut to this version's schema - rootosch := *cc.Props.Spec.Versions[i-1].Schema.OpenAPIV3Schema - - // construct a map of all paths using x-kubernetes-* OpenAPI extensions - extAttrs := xKubernetesAttributes(defpath.Selectors(), rootosch) - // construct a map of all the paths that have x-kubernetes-embedded-resource: true defined preserve := make(map[string]bool) var walkfn func(path []cue.Selector, sch v1.JSONSchemaProps) error @@ -245,11 +253,6 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { var stack []ast.Node var pathstack []cue.Selector - ast.Walk(schast, func(n ast.Node) bool { - fmt.Printf("n: %v\n", n) - return true - }, nil) - astutil.Apply(schast, func(c astutil.Cursor) bool { // Skip the root if c.Parent() == nil { @@ -283,9 +286,9 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { x.Elts = newlist } - if attr, ok := extAttrs[pathKey]; ok { - x.Elts = append(x.Elts, &attr) - } + // if attr, ok := extAttrs[pathKey]; ok { + // x.Elts = append(x.Elts, &attr) + // } // TODO: how do I get the path for this case? // case *ast.Field: // if attr, ok := extAttrs[pathKey]; ok { @@ -355,6 +358,7 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { Schema: ctx.BuildFile(schast), }) } + return cc, nil } @@ -409,30 +413,38 @@ const ( ) // Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them -func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) map[string]ast.Attribute { - extensions := map[string]ast.Attribute{} - - pathKey := cue.MakePath(path...).String() - - attrBody := []string{} +func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) []struct { + path []cue.Selector + attr *ast.Attribute +} { + extensions := []struct { + path []cue.Selector + attr *ast.Attribute + }{} + + // attrFields := []string{} + + attrBody := make([]string, 0) addAttr := func(key XExtensionAttr, val string) { attrBody = append(attrBody, fmt.Sprintf("%s=%s", key, val)) } if prop.XPreserveUnknownFields != nil { + // attrFields = append(attrFields, fmt.Sprintf("%s=%t", XPreserveUnknownFields, *prop.XPreserveUnknownFields)) addAttr(XPreserveUnknownFields, fmt.Sprintf("%t", *prop.XPreserveUnknownFields)) } if prop.XEmbeddedResource { - addAttr(XEmbeddedResource, fmt.Sprintf("%t", *prop.XPreserveUnknownFields)) + // attrFields = append(attrFields, fmt.Sprintf("%s=%t", XEmbeddedResource, *prop.XPreserveUnknownFields)) + addAttr(XEmbeddedResource, fmt.Sprintf("%t", prop.XEmbeddedResource)) } if prop.XIntOrString { - addAttr(XIntOrString, fmt.Sprintf("%t", *prop.XPreserveUnknownFields)) + addAttr(XIntOrString, fmt.Sprintf("%t", prop.XIntOrString)) } if len(prop.XListMapKeys) > 0 { - addAttr(XListMapKeys, fmt.Sprintf(`["%s"])`, strings.Join(prop.XListMapKeys, `", "`))) + addAttr(XListMapKeys, fmt.Sprintf(`'["%s"]'`, strings.Join(prop.XListMapKeys, `", "`))) } if prop.XListType != nil { @@ -448,18 +460,26 @@ func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) map[str if err != nil { panic(err) } - addAttr(XValidations, `"`+string(vals)+`"`) + addAttr(XValidations, "\"\"\"\n"+string(vals)+"\n\"\"\"") } - attrText := fmt.Sprintf("@%s(%s)", "xKubernetes", strings.Join(attrBody, ", ")) - extensions[pathKey] = ast.Attribute{Text: attrText} + if len(attrBody) > 0 { + attrText := fmt.Sprintf("@%s(%s)", "crd", strings.Join(attrBody, ", ")) + extensions = append(extensions, struct { + path []cue.Selector + attr *ast.Attribute + }{ + path: path, + attr: &ast.Attribute{Text: attrText}, + }) + + fmt.Println(cue.MakePath(path...).String() + ": " + attrText) + } - for name, nextProp := range prop.Properties { + for nextPath := range prop.Properties { // Recursively add subextensions for each property\ - subExts := xKubernetesAttributes(append(path, cue.Str(name)), nextProp) - for key, val := range subExts { - extensions[key] = val - } + subExts := xKubernetesAttributes(append(path, cue.Str(nextPath)), prop.Properties[nextPath]) + extensions = append(extensions, subExts...) } return extensions From 5744b9fa90636a0b8b20012613596b7fb20bde52 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Tue, 14 Nov 2023 18:24:02 -0500 Subject: [PATCH 08/20] remove debug --- encoding/crd/extract.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encoding/crd/extract.go b/encoding/crd/extract.go index 379aeb46c89..461743a186d 100644 --- a/encoding/crd/extract.go +++ b/encoding/crd/extract.go @@ -473,7 +473,7 @@ func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) []struc attr: &ast.Attribute{Text: attrText}, }) - fmt.Println(cue.MakePath(path...).String() + ": " + attrText) + // fmt.Println(cue.MakePath(path...).String() + ": " + attrText) } for nextPath := range prop.Properties { From 31c29674f05dcccedbd6c6611f4e5961e817d975 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Tue, 14 Nov 2023 18:28:23 -0500 Subject: [PATCH 09/20] rename file to decode --- cmd/cue/cmd/get_crd.go | 2 +- encoding/crd/{extract.go => decode.go} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename encoding/crd/{extract.go => decode.go} (98%) diff --git a/cmd/cue/cmd/get_crd.go b/cmd/cue/cmd/get_crd.go index ebdb80ca47e..f7e5f64d762 100644 --- a/cmd/cue/cmd/get_crd.go +++ b/cmd/cue/cmd/get_crd.go @@ -68,7 +68,7 @@ func runGetCRD(cmd *Command, args []string) error { return err } - crds, err := decoder.Generate(data) + crds, err := decoder.Decode(data) if err != nil { return err } diff --git a/encoding/crd/extract.go b/encoding/crd/decode.go similarity index 98% rename from encoding/crd/extract.go rename to encoding/crd/decode.go index 461743a186d..262bec0961c 100644 --- a/encoding/crd/extract.go +++ b/encoding/crd/decode.go @@ -30,10 +30,10 @@ func NewDecoder(ctx *cue.Context, header string) *Decoder { } } -// Generate takes a multi-doc YAML containing Kubernetes CRDs and returns the CUE definitions +// Decode takes a multi-doc YAML containing Kubernetes CRDs and returns the CUE definitions // generated from the OpenAPI spec. The resulting key value pairs, contain a unique identifier // in the format `//` and the contents of the CUE definition. -func (imp *Decoder) Generate(crdData []byte) (map[string][]byte, error) { +func (imp *Decoder) Decode(crdData []byte) (map[string][]byte, error) { result := make(map[string][]byte) crds, err := imp.fromYAML(crdData) From 06627bc1871fe26523402ec26f28fa4ab8d69ec4 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Tue, 14 Nov 2023 19:15:06 -0500 Subject: [PATCH 10/20] start work on arrays --- encoding/crd/decode.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/encoding/crd/decode.go b/encoding/crd/decode.go index 262bec0961c..83ccef815bb 100644 --- a/encoding/crd/decode.go +++ b/encoding/crd/decode.go @@ -477,10 +477,25 @@ func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) []struc } for nextPath := range prop.Properties { - // Recursively add subextensions for each property\ + // Recursively add subextensions for each property subExts := xKubernetesAttributes(append(path, cue.Str(nextPath)), prop.Properties[nextPath]) extensions = append(extensions, subExts...) } + // TODO: array does not work right, see https://github.com/istio/istio/blob/0d5f530188dfe571bf0d8f515618ba99a0dc3e6c/manifests/charts/base/crds/crd-all.gen.yaml#L188 + if prop.Type == "array" { + if len(prop.Items.JSONSchemas) > 0 { + for _, nextProp := range prop.Items.JSONSchemas { + // Recursively add subextensions for each property + subExts := xKubernetesAttributes(append(path, cue.AnyIndex), nextProp) + extensions = append(extensions, subExts...) + } + } else { + // Recursively add subextensions for each property + subExts := xKubernetesAttributes(append(path, cue.AnyIndex), *prop.Items.Schema) + extensions = append(extensions, subExts...) + } + } + return extensions } From b31e83c5859956c182b8fc0bd941ca09618ab9b5 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Wed, 15 Nov 2023 11:09:03 -0500 Subject: [PATCH 11/20] fix license headers --- cmd/cue/cmd/get_crd.go | 2 +- encoding/crd/decode.go | 16 ++++++++++++++++ encoding/crd/unmarshal.go | 22 ++++++++++++++++------ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/cmd/cue/cmd/get_crd.go b/cmd/cue/cmd/get_crd.go index f7e5f64d762..26b2bfa32ee 100644 --- a/cmd/cue/cmd/get_crd.go +++ b/cmd/cue/cmd/get_crd.go @@ -1,4 +1,4 @@ -// Copyright 2018 The CUE Authors +// Copyright 2023 The CUE Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/encoding/crd/decode.go b/encoding/crd/decode.go index 83ccef815bb..25331f77671 100644 --- a/encoding/crd/decode.go +++ b/encoding/crd/decode.go @@ -1,3 +1,18 @@ +/* +Copyright 2023 Stefan Prodan + +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 crd import ( @@ -176,6 +191,7 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { // Add attributes for k8s oapi extensions // construct a map of all paths using x-kubernetes-* OpenAPI extensions + // TODO: what is the list type? for _, extAttr := range xKubernetesAttributes(defpath.Selectors(), rootosch) { extendedVal := extracted.LookupPath(cue.MakePath(extAttr.path...)) node := extendedVal.Source() diff --git a/encoding/crd/unmarshal.go b/encoding/crd/unmarshal.go index 72383668a18..aa5ea73b0f6 100644 --- a/encoding/crd/unmarshal.go +++ b/encoding/crd/unmarshal.go @@ -1,3 +1,18 @@ +/* +Copyright 2023 Stefan Prodan + +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 crd import ( @@ -12,11 +27,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" ) -var ( - // The "codec factory" that can decode CRDs - codecs = serializer.NewCodecFactory(scheme.Scheme, serializer.EnableStrict) -) - // Unmarshals a YAML file containing one or more CustomResourceDefinitions // into a list of CRD objects func UnmarshalFile(ctx *cue.Context, filename string) ([]*v1.CustomResourceDefinition, error) { @@ -76,7 +86,7 @@ func UnmarshalOne(val cue.Value) (*v1.CustomResourceDefinition, error) { // Decode into a v1.CustomResourceDefinition obj := &v1.CustomResourceDefinition{} - if err := runtime.DecodeInto(codecs.UniversalDecoder(), d, obj); err != nil { + if err = runtime.DecodeInto(serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder(), d, obj); err != nil { return nil, err } From a945e4d5e472ffe663e7be1e4faaab2b4992e3a9 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Mon, 20 Nov 2023 17:04:03 -0500 Subject: [PATCH 12/20] an attempt at list attrs --- encoding/crd/decode.go | 201 ++++++++++++++++++++++++++++++++++------- go.mod | 9 +- go.sum | 26 ------ 3 files changed, 168 insertions(+), 68 deletions(-) diff --git a/encoding/crd/decode.go b/encoding/crd/decode.go index 25331f77671..3a7bd2a580a 100644 --- a/encoding/crd/decode.go +++ b/encoding/crd/decode.go @@ -189,20 +189,6 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { // first, extract and get the schema handle itself extracted := ctx.BuildFile(of) - // Add attributes for k8s oapi extensions - // construct a map of all paths using x-kubernetes-* OpenAPI extensions - // TODO: what is the list type? - for _, extAttr := range xKubernetesAttributes(defpath.Selectors(), rootosch) { - extendedVal := extracted.LookupPath(cue.MakePath(extAttr.path...)) - node := extendedVal.Source() - switch x := node.(type) { - case *ast.StructLit: - x.Elts = append(x.Elts, extAttr.attr) - case *ast.Field: - x.Attrs = append(x.Attrs, extAttr.attr) - } - } - // then unify with our desired base constraints nsConstraint := "!" if cc.Props.Spec.Scope != "Namespaced" { @@ -222,9 +208,27 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { } `, cc.Props.Spec.Group, ver, kname, nsConstraint))) - // for path, attr := range extAttrs { - // cue.MakePath(cue) - // sch.LookupPath(cue.MakePath(cue.Str(path))) + // Add attributes for k8s oapi extensions + // construct a map of all paths using x-kubernetes-* OpenAPI extensions + // TODO: what is the list type? + sch = mapAttributes(sch, rootosch) + // for _, extAttr := range xKubernetesAttributes(defpath.Selectors(), rootosch) { + // attrPath := cue.MakePath(extAttr.path...) + // sch = sch.FillPath(attrPath, extAttr.attr) + // // extendedVal := sch.LookupPath(attrPath) + // // switch x := extendedVal.Source().(type) { + // // case *ast.StructLit: + // // x.Elts = append(x.Elts, extAttr.attr) + // // case *ast.Field: + // // x.Attrs = append(x.Attrs, extAttr.attr) + // // case *ast.File: + // // x.Decls = append(x.Decls, extAttr.attr) + // // default: + // // fmt.Println(attrPath) + // // fmt.Printf("extendedVal: %v\n\n", extendedVal) + // // // fmt.Println(reflect.TypeOf(node)) + // // // fmt.Printf("node: %v\n", node) + // // } // } // now, go back to an AST because it's easier to manipulate references there @@ -301,15 +305,6 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { } x.Elts = newlist } - - // if attr, ok := extAttrs[pathKey]; ok { - // x.Elts = append(x.Elts, &attr) - // } - // TODO: how do I get the path for this case? - // case *ast.Field: - // if attr, ok := extAttrs[pathKey]; ok { - // x.Attrs = append(x.Attrs, &attr) - // } } return true }, nil) @@ -498,20 +493,158 @@ func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) []struc extensions = append(extensions, subExts...) } + // TODO: array does not work right, see https://github.com/istio/istio/blob/0d5f530188dfe571bf0d8f515618ba99a0dc3e6c/manifests/charts/base/crds/crd-all.gen.yaml#L188 + // if prop.Type == "array" { + // if len(prop.Items.JSONSchemas) > 0 { + // for i, _ := n.List(); i.Next(); { + // a = append(a, i.Value()) + // } + // // Iterate each schema in list and add attribute for that list index + // // for i, nextProp := range prop.Items.JSONSchemas { + // // // Add attribute to the item at index i + // // } + // } else { + // // Add attribute to the pattern constraint + // // // Recursively add subextensions for each property + // // subExts := xKubernetesAttributes(append(path, cue.AnyIndex), *prop.Items.Schema) + // // extensions = append(extensions, subExts...) + // } + // } + + return extensions +} + +// TODO: use internal.Attr if it can support writing attributes +type attr struct { + name string + fields []keyval +} + +func (a attr) String() string { + fields := []string{} + for _, f := range a.fields { + fields = append(fields, f.String()) + } + return fmt.Sprintf("@%s(%s)", a.name, strings.Join(fields, ", ")) +} + +func addAttr(field ast.Label, a attr) *ast.Field { + return &ast.Field{ + Label: field, + Value: ast.NewIdent("_"), + Attrs: []*ast.Attribute{ + {Text: a.String()}, + }, + } +} + +type keyval struct { + key string + val string +} + +func (kv keyval) String() string { + if kv.val != "" { + return kv.key + "=" + kv.val + } + return kv.key +} + +// Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them +func mapAttributes(val cue.Value, prop v1.JSONSchemaProps) cue.Value { + a := attr{ + name: "crd", + fields: []keyval{}, + } + + attrBody := make([]string, 0) + appendField := func(key XExtensionAttr, val string) { + a.fields = append(a.fields, keyval{key: string(key), val: val}) + attrBody = append(attrBody, fmt.Sprintf("%s=%s", key, val)) + } + + if prop.XPreserveUnknownFields != nil { + appendField(XPreserveUnknownFields, fmt.Sprintf("%t", *prop.XPreserveUnknownFields)) + } + + if prop.XEmbeddedResource { + appendField(XEmbeddedResource, fmt.Sprintf("%t", prop.XEmbeddedResource)) + } + + if prop.XIntOrString { + appendField(XIntOrString, fmt.Sprintf("%t", prop.XIntOrString)) + } + + if len(prop.XListMapKeys) > 0 { + appendField(XListMapKeys, fmt.Sprint(val.Context().Encode(prop.XListMapKeys))) + } + + if prop.XListType != nil { + appendField(XListType, fmt.Sprintf("%q", *prop.XListType)) + } + + if prop.XMapType != nil { + appendField(XMapType, fmt.Sprintf("%q", *prop.XMapType)) + } + + if len(prop.XValidations) > 0 { + appendField(XValidations, fmt.Sprint(val.Context().Encode(prop.XValidations))) + } + + if len(a.fields) > 0 { + attr := &ast.Attribute{Text: a.String()} + node := val.Source() + switch x := node.(type) { + case *ast.StructLit: + x.Elts = append(x.Elts, attr) + case *ast.Field: + x.Attrs = append(x.Attrs, attr) + case *ast.File: + x.Decls = append(x.Decls, attr) + default: + // fmt.Println(attrPath) + // fmt.Printf("extendedVal: %v\n\n", extendedVal) + // fmt.Println(reflect.TypeOf(node)) + // fmt.Printf("node: %v\n", node) + } + } + + for name := range prop.Properties { + // Recursively add subextensions for each property + nextPath := cue.MakePath(cue.Str(name)) + nextVal := mapAttributes(val.LookupPath(nextPath), prop.Properties[name]) + val = val.FillPath(nextPath, nextVal) + } + // TODO: array does not work right, see https://github.com/istio/istio/blob/0d5f530188dfe571bf0d8f515618ba99a0dc3e6c/manifests/charts/base/crds/crd-all.gen.yaml#L188 if prop.Type == "array" { if len(prop.Items.JSONSchemas) > 0 { - for _, nextProp := range prop.Items.JSONSchemas { - // Recursively add subextensions for each property - subExts := xKubernetesAttributes(append(path, cue.AnyIndex), nextProp) - extensions = append(extensions, subExts...) + iter, err := val.List() + if err != nil { + fmt.Println(err) + } + // Iterate each schema in list and add attribute for that list index + for i := range prop.Items.JSONSchemas { + // Add attribute to the item at index i + if !iter.Next() { + break + } + nextVal := iter.Value() + val = val.FillPath(nextVal.Path(), mapAttributes(nextVal, prop.Items.JSONSchemas[i])) } } else { - // Recursively add subextensions for each property - subExts := xKubernetesAttributes(append(path, cue.AnyIndex), *prop.Items.Schema) - extensions = append(extensions, subExts...) + if val.Allows(cue.AnyIndex) { + anyIndex := cue.MakePath(cue.AnyIndex) + nextVal := mapAttributes(val.LookupPath(anyIndex), *prop.Items.Schema) + val = val.FillPath(anyIndex, nextVal) + } + + // Add attribute to the pattern constraint + // // Recursively add subextensions for each property + // subExts := xKubernetesAttributes(append(path, cue.AnyIndex), *prop.Items.Schema) + // extensions = append(extensions, subExts...) } } - return extensions + return val } diff --git a/go.mod b/go.mod index 32128e3756e..74d5033ce4a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 github.com/cockroachdb/apd/v3 v3.2.1 github.com/emicklei/proto v1.10.0 - github.com/getkin/kin-openapi v0.120.0 github.com/go-quicktest/qt v1.101.0 github.com/google/go-cmp v0.5.9 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 @@ -31,23 +30,17 @@ require ( require ( github.com/go-logr/logr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/swag v0.22.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/invopop/yaml v0.2.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kr/text v0.2.0 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/perimeterx/marshmallow v1.1.5 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/sys v0.13.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.100.1 // indirect diff --git a/go.sum b/go.sum index 1d9531d91f5..d7d9eb6cdbd 100644 --- a/go.sum +++ b/go.sum @@ -9,19 +9,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= -github.com/getkin/kin-openapi v0.120.0 h1:MqJcNJFrMDFNc07iwE8iFC5eT2k/NPUFDIpNeiZv8Jg= -github.com/getkin/kin-openapi v0.120.0/go.mod h1:PCWw/lfBrJY4HcdqE3jj+QFkaFK8ABoqo7PvqVhXXqw= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -35,10 +27,6 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY= -github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -52,8 +40,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -61,16 +47,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= -github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -85,16 +67,10 @@ github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRM github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/tetratelabs/wazero v1.0.2 h1:lpwL5zczFHk2mxKur98035Gig+Z3vd9JURk6lUdZxXY= github.com/tetratelabs/wazero v1.0.2/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -143,8 +119,6 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= From 5cc67007f1e4088b58ee536ced2d571ba1b93a30 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Mon, 20 Nov 2023 17:23:01 -0500 Subject: [PATCH 13/20] more --- encoding/crd/decode.go | 136 +++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/encoding/crd/decode.go b/encoding/crd/decode.go index 3a7bd2a580a..0e66cc0e3aa 100644 --- a/encoding/crd/decode.go +++ b/encoding/crd/decode.go @@ -514,6 +514,70 @@ func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) []struc return extensions } +// Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them +func mapAttributes(val cue.Value, prop v1.JSONSchemaProps) cue.Value { + attr := xk8sattr(*val.Context(), prop) + if attr != nil { + _, p := val.ReferencePath() + fmt.Println(p.String() + ": " + attr.Text) + node := val.Source() + switch x := node.(type) { + case *ast.StructLit: + x.Elts = append(x.Elts, attr) + case *ast.Field: + x.Attrs = append(x.Attrs, attr) + case *ast.File: + x.Decls = append(x.Decls, attr) + default: + // fmt.Println(attrPath) + // fmt.Printf("extendedVal: %v\n\n", extendedVal) + // fmt.Println(reflect.TypeOf(node)) + // fmt.Printf("node: %v\n", node) + } + } + + for name := range prop.Properties { + // Recursively add subextensions for each property + nextPath := cue.MakePath(cue.Str(name)) + nextVal := mapAttributes(val.LookupPath(nextPath), prop.Properties[name]) + val = val.FillPath(nextPath, nextVal) + } + + // TODO: array does not work right, see https://github.com/istio/istio/blob/0d5f530188dfe571bf0d8f515618ba99a0dc3e6c/manifests/charts/base/crds/crd-all.gen.yaml#L188 + if prop.Type == "array" { + if len(prop.Items.JSONSchemas) > 0 { + iter, err := val.List() + if err != nil { + fmt.Println(err) + } + // Iterate each schema in list and add attribute for that list index + for i := range prop.Items.JSONSchemas { + // Add attribute to the item at index i + if !iter.Next() { + break + } + nextVal := iter.Value() + val = val.FillPath(nextVal.Path(), mapAttributes(nextVal, prop.Items.JSONSchemas[i])) + } + } else { + // if val.Allows(cue.AnyIndex) { + anyIndex := cue.MakePath(cue.AnyIndex) + nextVal := mapAttributes(val.LookupPath(anyIndex), *prop.Items.Schema) + val = val.FillPath(anyIndex, nextVal) + // } else { + // fmt.Println("here") + // } + + // Add attribute to the pattern constraint + // // Recursively add subextensions for each property + // subExts := xKubernetesAttributes(append(path, cue.AnyIndex), *prop.Items.Schema) + // extensions = append(extensions, subExts...) + } + } + + return val +} + // TODO: use internal.Attr if it can support writing attributes type attr struct { name string @@ -528,16 +592,6 @@ func (a attr) String() string { return fmt.Sprintf("@%s(%s)", a.name, strings.Join(fields, ", ")) } -func addAttr(field ast.Label, a attr) *ast.Field { - return &ast.Field{ - Label: field, - Value: ast.NewIdent("_"), - Attrs: []*ast.Attribute{ - {Text: a.String()}, - }, - } -} - type keyval struct { key string val string @@ -550,8 +604,7 @@ func (kv keyval) String() string { return kv.key } -// Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them -func mapAttributes(val cue.Value, prop v1.JSONSchemaProps) cue.Value { +func xk8sattr(ctx cue.Context, prop v1.JSONSchemaProps) *ast.Attribute { a := attr{ name: "crd", fields: []keyval{}, @@ -576,7 +629,7 @@ func mapAttributes(val cue.Value, prop v1.JSONSchemaProps) cue.Value { } if len(prop.XListMapKeys) > 0 { - appendField(XListMapKeys, fmt.Sprint(val.Context().Encode(prop.XListMapKeys))) + appendField(XListMapKeys, fmt.Sprint(ctx.Encode(prop.XListMapKeys))) } if prop.XListType != nil { @@ -588,63 +641,12 @@ func mapAttributes(val cue.Value, prop v1.JSONSchemaProps) cue.Value { } if len(prop.XValidations) > 0 { - appendField(XValidations, fmt.Sprint(val.Context().Encode(prop.XValidations))) + appendField(XValidations, fmt.Sprint(ctx.Encode(prop.XValidations))) } if len(a.fields) > 0 { - attr := &ast.Attribute{Text: a.String()} - node := val.Source() - switch x := node.(type) { - case *ast.StructLit: - x.Elts = append(x.Elts, attr) - case *ast.Field: - x.Attrs = append(x.Attrs, attr) - case *ast.File: - x.Decls = append(x.Decls, attr) - default: - // fmt.Println(attrPath) - // fmt.Printf("extendedVal: %v\n\n", extendedVal) - // fmt.Println(reflect.TypeOf(node)) - // fmt.Printf("node: %v\n", node) - } + return &ast.Attribute{Text: a.String()} } - for name := range prop.Properties { - // Recursively add subextensions for each property - nextPath := cue.MakePath(cue.Str(name)) - nextVal := mapAttributes(val.LookupPath(nextPath), prop.Properties[name]) - val = val.FillPath(nextPath, nextVal) - } - - // TODO: array does not work right, see https://github.com/istio/istio/blob/0d5f530188dfe571bf0d8f515618ba99a0dc3e6c/manifests/charts/base/crds/crd-all.gen.yaml#L188 - if prop.Type == "array" { - if len(prop.Items.JSONSchemas) > 0 { - iter, err := val.List() - if err != nil { - fmt.Println(err) - } - // Iterate each schema in list and add attribute for that list index - for i := range prop.Items.JSONSchemas { - // Add attribute to the item at index i - if !iter.Next() { - break - } - nextVal := iter.Value() - val = val.FillPath(nextVal.Path(), mapAttributes(nextVal, prop.Items.JSONSchemas[i])) - } - } else { - if val.Allows(cue.AnyIndex) { - anyIndex := cue.MakePath(cue.AnyIndex) - nextVal := mapAttributes(val.LookupPath(anyIndex), *prop.Items.Schema) - val = val.FillPath(anyIndex, nextVal) - } - - // Add attribute to the pattern constraint - // // Recursively add subextensions for each property - // subExts := xKubernetesAttributes(append(path, cue.AnyIndex), *prop.Items.Schema) - // extensions = append(extensions, subExts...) - } - } - - return val + return nil } From 8aa9ac4e6439c48cf685470b871c6f7f702e5ebd Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Wed, 22 Nov 2023 14:04:30 -0500 Subject: [PATCH 14/20] remove k8s deps by vendoring types this breaks compatibility with all but v1 of the CRD API since the data is no longer converted using the k8s apimachinery library Signed-off-by: Justen Stall --- encoding/crd/decode.go | 19 +- encoding/crd/k8s/apiextensions/types.go | 422 +++++ .../crd/k8s/apiextensions/types_jsonschema.go | 288 ++++ encoding/crd/k8s/metav1/time.go | 182 ++ encoding/crd/k8s/metav1/types.go | 1480 +++++++++++++++++ encoding/crd/unmarshal.go | 19 +- go.mod | 15 - go.sum | 71 - 8 files changed, 2392 insertions(+), 104 deletions(-) create mode 100644 encoding/crd/k8s/apiextensions/types.go create mode 100644 encoding/crd/k8s/apiextensions/types_jsonschema.go create mode 100644 encoding/crd/k8s/metav1/time.go create mode 100644 encoding/crd/k8s/metav1/types.go diff --git a/encoding/crd/decode.go b/encoding/crd/decode.go index 0e66cc0e3aa..b59bcb94fad 100644 --- a/encoding/crd/decode.go +++ b/encoding/crd/decode.go @@ -26,9 +26,9 @@ import ( "cuelang.org/go/cue/ast/astutil" "cuelang.org/go/cue/format" "cuelang.org/go/cue/token" + "cuelang.org/go/encoding/crd/k8s/apiextensions" "cuelang.org/go/encoding/openapi" "cuelang.org/go/encoding/yaml" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) // Decoder generates CUE definitions from Kubernetes CRDs using the OpenAPI v3 spec. @@ -117,7 +117,7 @@ type IntermediateCRD struct { Original cue.Value // Object form of CRD, decoded by k8s decoder - Props *v1.CustomResourceDefinition + Props *apiextensions.CustomResourceDefinition // All the schemas in the original CRD, converted to CUE representation. Schemas []VersionedSchema @@ -244,8 +244,8 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { // construct a map of all the paths that have x-kubernetes-embedded-resource: true defined preserve := make(map[string]bool) - var walkfn func(path []cue.Selector, sch v1.JSONSchemaProps) error - walkfn = func(path []cue.Selector, sch v1.JSONSchemaProps) error { + var walkfn func(path []cue.Selector, sch apiextensions.JSONSchemaProps) error + walkfn = func(path []cue.Selector, sch apiextensions.JSONSchemaProps) error { if sch.XPreserveUnknownFields != nil { preserve[cue.MakePath(path...).String()] = *sch.XPreserveUnknownFields } @@ -424,7 +424,7 @@ const ( ) // Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them -func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) []struct { +func xKubernetesAttributes(path []cue.Selector, prop apiextensions.JSONSchemaProps) []struct { path []cue.Selector attr *ast.Attribute } { @@ -515,7 +515,7 @@ func xKubernetesAttributes(path []cue.Selector, prop v1.JSONSchemaProps) []struc } // Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them -func mapAttributes(val cue.Value, prop v1.JSONSchemaProps) cue.Value { +func mapAttributes(val cue.Value, prop apiextensions.JSONSchemaProps) cue.Value { attr := xk8sattr(*val.Context(), prop) if attr != nil { _, p := val.ReferencePath() @@ -562,7 +562,10 @@ func mapAttributes(val cue.Value, prop v1.JSONSchemaProps) cue.Value { } else { // if val.Allows(cue.AnyIndex) { anyIndex := cue.MakePath(cue.AnyIndex) - nextVal := mapAttributes(val.LookupPath(anyIndex), *prop.Items.Schema) + val.LookupPath(cue.MakePath(cue.AnyIndex)) + nextVal := val.LookupPath(anyIndex) + fmt.Println(nextVal) + nextVal = mapAttributes(nextVal, *prop.Items.Schema) val = val.FillPath(anyIndex, nextVal) // } else { // fmt.Println("here") @@ -604,7 +607,7 @@ func (kv keyval) String() string { return kv.key } -func xk8sattr(ctx cue.Context, prop v1.JSONSchemaProps) *ast.Attribute { +func xk8sattr(ctx cue.Context, prop apiextensions.JSONSchemaProps) *ast.Attribute { a := attr{ name: "crd", fields: []keyval{}, diff --git a/encoding/crd/k8s/apiextensions/types.go b/encoding/crd/k8s/apiextensions/types.go new file mode 100644 index 00000000000..aed482025af --- /dev/null +++ b/encoding/crd/k8s/apiextensions/types.go @@ -0,0 +1,422 @@ +/* +Copyright 2017 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 apiextensions + +import ( + metav1 "cuelang.org/go/encoding/crd/k8s/metav1" +) + +// ConversionStrategyType describes different conversion types. +type ConversionStrategyType string + +const ( + // NoneConverter is a converter that only sets apiversion of the CR and leave everything else unchanged. + NoneConverter ConversionStrategyType = "None" + // WebhookConverter is a converter that calls to an external webhook to convert the CR. + WebhookConverter ConversionStrategyType = "Webhook" +) + +// CustomResourceDefinitionSpec describes how a user wants their resource to appear +type CustomResourceDefinitionSpec struct { + // Group is the group this resource belongs in + Group string + // Version is the version this resource belongs in + // Should be always first item in Versions field if provided. + // Optional, but at least one of Version or Versions must be set. + // Deprecated: Please use `Versions`. + Version string + // Names are the names used to describe this custom resource + Names CustomResourceDefinitionNames + // Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced + Scope ResourceScope + // Validation describes the validation methods for CustomResources + // Optional, the global validation schema for all versions. + // Top-level and per-version schemas are mutually exclusive. + // +optional + Validation *CustomResourceValidation + // Subresources describes the subresources for CustomResource + // Optional, the global subresources for all versions. + // Top-level and per-version subresources are mutually exclusive. + // +optional + Subresources *CustomResourceSubresources + // Versions is the list of all supported versions for this resource. + // If Version field is provided, this field is optional. + // Validation: All versions must use the same validation schema for now. i.e., top + // level Validation field is applied to all of these versions. + // Order: The version name will be used to compute the order. + // If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered + // lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version), + // then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first + // by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing + // major version, then minor version. An example sorted list of versions: + // v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10. + Versions []CustomResourceDefinitionVersion + // AdditionalPrinterColumns are additional columns shown e.g. in kubectl next to the name. Defaults to a created-at column. + // Optional, the global columns for all versions. + // Top-level and per-version columns are mutually exclusive. + // +optional + AdditionalPrinterColumns []CustomResourceColumnDefinition + + // `conversion` defines conversion settings for the CRD. + Conversion *CustomResourceConversion + + // preserveUnknownFields disables pruning of object fields which are not + // specified in the OpenAPI schema. apiVersion, kind, metadata and known + // fields inside metadata are always preserved. + // Defaults to true in v1beta and will default to false in v1. + PreserveUnknownFields *bool +} + +// CustomResourceConversion describes how to convert different versions of a CR. +type CustomResourceConversion struct { + // `strategy` specifies the conversion strategy. Allowed values are: + // - `None`: The converter only change the apiVersion and would not touch any other field in the CR. + // - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information + // is needed for this option. This requires spec.preserveUnknownFields to be false. + Strategy ConversionStrategyType + + // `webhookClientConfig` is the instructions for how to call the webhook if strategy is `Webhook`. + WebhookClientConfig *WebhookClientConfig + + // ConversionReviewVersions is an ordered list of preferred `ConversionReview` + // versions the Webhook expects. API server will try to use first version in + // the list which it supports. If none of the versions specified in this list + // supported by API server, conversion will fail for this object. + // If a persisted Webhook configuration specifies allowed versions and does not + // include any versions known to the API Server, calls to the webhook will fail. + // +optional + ConversionReviewVersions []string +} + +// WebhookClientConfig contains the information to make a TLS +// connection with the webhook. It has the same field as admissionregistration.internal.WebhookClientConfig. +type WebhookClientConfig struct { + // `url` gives the location of the webhook, in standard URL form + // (`scheme://host:port/path`). Exactly one of `url` or `service` + // must be specified. + // + // The `host` should not refer to a service running in the cluster; use + // the `service` field instead. The host might be resolved via external + // DNS in some apiservers (e.g., `kube-apiserver` cannot resolve + // in-cluster DNS as that would be a layering violation). `host` may + // also be an IP address. + // + // Please note that using `localhost` or `127.0.0.1` as a `host` is + // risky unless you take great care to run this webhook on all hosts + // which run an apiserver which might need to make calls to this + // webhook. Such installs are likely to be non-portable, i.e., not easy + // to turn up in a new cluster. + // + // The scheme must be "https"; the URL must begin with "https://". + // + // A path is optional, and if present may be any string permissible in + // a URL. You may use the path to pass an arbitrary string to the + // webhook, for example, a cluster identifier. + // + // Attempting to use a user or basic auth e.g. "user:password@" is not + // allowed. Fragments ("#...") and query parameters ("?...") are not + // allowed, either. + // + // +optional + URL *string + + // `service` is a reference to the service for this webhook. Either + // `service` or `url` must be specified. + // + // If the webhook is running within the cluster, then you should use `service`. + // + // +optional + Service *ServiceReference + + // `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // If unspecified, system trust roots on the apiserver are used. + // +optional + CABundle []byte +} + +// ServiceReference holds a reference to Service.legacy.k8s.io +type ServiceReference struct { + // `namespace` is the namespace of the service. + // Required + Namespace string + // `name` is the name of the service. + // Required + Name string + + // `path` is an optional URL path which will be sent in any request to + // this service. + // +optional + Path *string + + // If specified, the port on the service that hosting webhook. + // `port` should be a valid port number (1-65535, inclusive). + // +optional + Port int32 +} + +// CustomResourceDefinitionVersion describes a version for CRD. +type CustomResourceDefinitionVersion struct { + // Name is the version name, e.g. “v1”, “v2beta1”, etc. + Name string + // Served is a flag enabling/disabling this version from being served via REST APIs + Served bool + // Storage flags the version as storage version. There must be exactly one flagged + // as storage version. + Storage bool + // deprecated indicates this version of the custom resource API is deprecated. + // When set to true, API requests to this version receive a warning header in the server response. + // Defaults to false. + Deprecated bool + // deprecationWarning overrides the default warning returned to API clients. + // May only be set when `deprecated` is true. + // The default warning indicates this version is deprecated and recommends use + // of the newest served version of equal or greater stability, if one exists. + DeprecationWarning *string + // Schema describes the schema for CustomResource used in validation, pruning, and defaulting. + // Top-level and per-version schemas are mutually exclusive. + // Per-version schemas must not all be set to identical values (top-level validation schema should be used instead) + // This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. + // +optional + Schema *CustomResourceValidation + // Subresources describes the subresources for CustomResource + // Top-level and per-version subresources are mutually exclusive. + // Per-version subresources must not all be set to identical values (top-level subresources should be used instead) + // This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. + // +optional + Subresources *CustomResourceSubresources + // AdditionalPrinterColumns are additional columns shown e.g. in kubectl next to the name. Defaults to a created-at column. + // Top-level and per-version columns are mutually exclusive. + // Per-version columns must not all be set to identical values (top-level columns should be used instead) + // This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. + // NOTE: CRDs created prior to 1.13 populated the top-level additionalPrinterColumns field by default. To apply an + // update that changes to per-version additionalPrinterColumns, the top-level additionalPrinterColumns field must + // be explicitly set to null + // +optional + AdditionalPrinterColumns []CustomResourceColumnDefinition +} + +// CustomResourceColumnDefinition specifies a column for server side printing. +type CustomResourceColumnDefinition struct { + // name is a human readable name for the column. + Name string + // type is an OpenAPI type definition for this column. + // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. + Type string + // format is an optional OpenAPI type definition for this column. The 'name' format is applied + // to the primary identifier column to assist in clients identifying column is the resource name. + // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. + Format string + // description is a human readable description of this column. + Description string + // priority is an integer defining the relative importance of this column compared to others. Lower + // numbers are considered higher priority. Columns that may be omitted in limited space scenarios + // should be given a higher priority. + Priority int32 + + // JSONPath is a simple JSON path, i.e. without array notation. + JSONPath string +} + +// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition +type CustomResourceDefinitionNames struct { + // Plural is the plural name of the resource to serve. It must match the name of the CustomResourceDefinition-registration + // too: plural.group and it must be all lowercase. + Plural string + // Singular is the singular name of the resource. It must be all lowercase Defaults to lowercased + Singular string + // ShortNames are short names for the resource. It must be all lowercase. + ShortNames []string + // Kind is the serialized kind of the resource. It is normally CamelCase and singular. + Kind string + // ListKind is the serialized kind of the list for this resource. Defaults to List. + ListKind string + // Categories is a list of grouped resources custom resources belong to (e.g. 'all') + // +optional + Categories []string +} + +// ResourceScope is an enum defining the different scopes available to a custom resource +type ResourceScope string + +const ( + ClusterScoped ResourceScope = "Cluster" + NamespaceScoped ResourceScope = "Namespaced" +) + +type ConditionStatus string + +// These are valid condition statuses. "ConditionTrue" means a resource is in the condition. +// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes +// can't decide if a resource is in the condition or not. In the future, we could add other +// intermediate conditions, e.g. ConditionDegraded. +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" + ConditionUnknown ConditionStatus = "Unknown" +) + +// CustomResourceDefinitionConditionType is a valid value for CustomResourceDefinitionCondition.Type +type CustomResourceDefinitionConditionType string + +const ( + // Established means that the resource has become active. A resource is established when all names are + // accepted without a conflict for the first time. A resource stays established until deleted, even during + // a later NamesAccepted due to changed names. Note that not all names can be changed. + Established CustomResourceDefinitionConditionType = "Established" + // NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in + // the group and are therefore accepted. + NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted" + // NonStructuralSchema means that one or more OpenAPI schema is not structural. + // + // A schema is structural if it specifies types for all values, with the only exceptions of those with + // - x-kubernetes-int-or-string: true — for fields which can be integer or string + // - x-kubernetes-preserve-unknown-fields: true — for raw, unspecified JSON values + // and there is no type, additionalProperties, default, nullable or x-kubernetes-* vendor extenions + // specified under allOf, anyOf, oneOf or not. + // + // Non-structural schemas will not be allowed anymore in v1 API groups. Moreover, new features will not be + // available for non-structural CRDs: + // - pruning + // - defaulting + // - read-only + // - OpenAPI publishing + // - webhook conversion + NonStructuralSchema CustomResourceDefinitionConditionType = "NonStructuralSchema" + // Terminating means that the CustomResourceDefinition has been deleted and is cleaning up. + Terminating CustomResourceDefinitionConditionType = "Terminating" + // KubernetesAPIApprovalPolicyConformant indicates that an API in *.k8s.io or *.kubernetes.io is or is not approved. For CRDs + // outside those groups, this condition will not be set. For CRDs inside those groups, the condition will + // be true if .metadata.annotations["api-approved.kubernetes.io"] is set to a URL, otherwise it will be false. + // See https://github.com/kubernetes/enhancements/pull/1111 for more details. + KubernetesAPIApprovalPolicyConformant CustomResourceDefinitionConditionType = "KubernetesAPIApprovalPolicyConformant" +) + +// CustomResourceDefinitionCondition contains details for the current condition of this pod. +type CustomResourceDefinitionCondition struct { + // Type is the type of the condition. Types include Established, NamesAccepted and Terminating. + Type CustomResourceDefinitionConditionType + // Status is the status of the condition. + // Can be True, False, Unknown. + Status ConditionStatus + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time + // Unique, one-word, CamelCase reason for the condition's last transition. + // +optional + Reason string + // Human-readable message indicating details about last transition. + // +optional + Message string +} + +// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition +type CustomResourceDefinitionStatus struct { + // Conditions indicate state for particular aspects of a CustomResourceDefinition + // +listType=map + // +listMapKey=type + Conditions []CustomResourceDefinitionCondition + + // AcceptedNames are the names that are actually being used to serve discovery + // They may be different than the names in spec. + AcceptedNames CustomResourceDefinitionNames + + // StoredVersions are all versions of CustomResources that were ever persisted. Tracking these + // versions allows a migration path for stored versions in etcd. The field is mutable + // so the migration controller can first finish a migration to another version (i.e. + // that no old objects are left in the storage), and then remove the rest of the + // versions from this list. + // None of the versions in this list can be removed from the spec.Versions field. + StoredVersions []string +} + +// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of +// a CustomResourceDefinition +const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io" + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format +// <.spec.name>.<.spec.group>. +type CustomResourceDefinition struct { + metav1.TypeMeta + metav1.ObjectMeta + + // Spec describes how the user wants the resources to appear + Spec CustomResourceDefinitionSpec + // Status indicates the actual state of the CustomResourceDefinition + Status CustomResourceDefinitionStatus +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CustomResourceDefinitionList is a list of CustomResourceDefinition objects. +type CustomResourceDefinitionList struct { + metav1.TypeMeta + metav1.ListMeta + + // Items individual CustomResourceDefinitions + Items []CustomResourceDefinition +} + +// CustomResourceValidation is a list of validation methods for CustomResources. +type CustomResourceValidation struct { + // OpenAPIV3Schema is the OpenAPI v3 schema to be validated against. + OpenAPIV3Schema *JSONSchemaProps +} + +// CustomResourceSubresources defines the status and scale subresources for CustomResources. +type CustomResourceSubresources struct { + // Status denotes the status subresource for CustomResources + Status *CustomResourceSubresourceStatus + // Scale denotes the scale subresource for CustomResources + Scale *CustomResourceSubresourceScale +} + +// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources. +// Status is represented by the `.status` JSON path inside of a CustomResource. When set, +// * exposes a /status subresource for the custom resource +// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza +// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza +type CustomResourceSubresourceStatus struct{} + +// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources. +type CustomResourceSubresourceScale struct { + // SpecReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Spec.Replicas. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .spec. + // If there is no value under the given path in the CustomResource, the /scale subresource will return an error on GET. + SpecReplicasPath string + // StatusReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Replicas. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .status. + // If there is no value under the given path in the CustomResource, the status replica value in the /scale subresource + // will default to 0. + StatusReplicasPath string + // LabelSelectorPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Selector. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under .status or .spec. + // Must be set to work with HPA. + // The field pointed by this JSON path must be a string field (not a complex selector struct) + // which contains a serialized label selector in string form. + // More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource + // If there is no value under the given path in the CustomResource, the status label selector value in the /scale + // subresource will default to the empty string. + // +optional + LabelSelectorPath *string +} diff --git a/encoding/crd/k8s/apiextensions/types_jsonschema.go b/encoding/crd/k8s/apiextensions/types_jsonschema.go new file mode 100644 index 00000000000..cc1c7437fcd --- /dev/null +++ b/encoding/crd/k8s/apiextensions/types_jsonschema.go @@ -0,0 +1,288 @@ +/* +Copyright 2017 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 apiextensions + +// FieldValueErrorReason is a machine-readable value providing more detail about why a field failed the validation. +// +enum +type FieldValueErrorReason string + +const ( + // FieldValueRequired is used to report required values that are not + // provided (e.g. empty strings, null values, or empty arrays). + FieldValueRequired FieldValueErrorReason = "FieldValueRequired" + // FieldValueDuplicate is used to report collisions of values that must be + // unique (e.g. unique IDs). + FieldValueDuplicate FieldValueErrorReason = "FieldValueDuplicate" + // FieldValueInvalid is used to report malformed values (e.g. failed regex + // match, too long, out of bounds). + FieldValueInvalid FieldValueErrorReason = "FieldValueInvalid" + // FieldValueForbidden is used to report valid (as per formatting rules) + // values which would be accepted under some conditions, but which are not + // permitted by the current conditions (such as security policy). + FieldValueForbidden FieldValueErrorReason = "FieldValueForbidden" +) + +// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/). +type JSONSchemaProps struct { + ID string + Schema JSONSchemaURL + Ref *string + Description string + Type string + Nullable bool + Format string + Title string + Default *JSON + Maximum *float64 + ExclusiveMaximum bool + Minimum *float64 + ExclusiveMinimum bool + MaxLength *int64 + MinLength *int64 + Pattern string + MaxItems *int64 + MinItems *int64 + UniqueItems bool + MultipleOf *float64 + Enum []JSON + MaxProperties *int64 + MinProperties *int64 + Required []string + Items *JSONSchemaPropsOrArray + AllOf []JSONSchemaProps + OneOf []JSONSchemaProps + AnyOf []JSONSchemaProps + Not *JSONSchemaProps + Properties map[string]JSONSchemaProps + AdditionalProperties *JSONSchemaPropsOrBool + PatternProperties map[string]JSONSchemaProps + Dependencies JSONSchemaDependencies + AdditionalItems *JSONSchemaPropsOrBool + Definitions JSONSchemaDefinitions + ExternalDocs *ExternalDocumentation + Example *JSON + + // x-kubernetes-preserve-unknown-fields stops the API server + // decoding step from pruning fields which are not specified + // in the validation schema. This affects fields recursively, + // but switches back to normal pruning behaviour if nested + // properties or additionalProperties are specified in the schema. + // This can either be true or undefined. False is forbidden. + XPreserveUnknownFields *bool + + // x-kubernetes-embedded-resource defines that the value is an + // embedded Kubernetes runtime.Object, with TypeMeta and + // ObjectMeta. The type must be object. It is allowed to further + // restrict the embedded object. Both ObjectMeta and TypeMeta + // are validated automatically. x-kubernetes-preserve-unknown-fields + // must be true. + XEmbeddedResource bool + + // x-kubernetes-int-or-string specifies that this value is + // either an integer or a string. If this is true, an empty + // type is allowed and type as child of anyOf is permitted + // if following one of the following patterns: + // + // 1) anyOf: + // - type: integer + // - type: string + // 2) allOf: + // - anyOf: + // - type: integer + // - type: string + // - ... zero or more + XIntOrString bool + + // x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used + // as the index of the map. + // + // This tag MUST only be used on lists that have the "x-kubernetes-list-type" + // extension set to "map". Also, the values specified for this attribute must + // be a scalar typed field of the child structure (no nesting is supported). + XListMapKeys []string + + // x-kubernetes-list-type annotates an array to further describe its topology. + // This extension must only be used on lists and may have 3 possible values: + // + // 1) `atomic`: the list is treated as a single entity, like a scalar. + // Atomic lists will be entirely replaced when updated. This extension + // may be used on any type of list (struct, scalar, ...). + // 2) `set`: + // Sets are lists that must not have multiple items with the same value. Each + // value must be a scalar, an object with x-kubernetes-map-type `atomic` or an + // array with x-kubernetes-list-type `atomic`. + // 3) `map`: + // These lists are like maps in that their elements have a non-index key + // used to identify them. Order is preserved upon merge. The map tag + // must only be used on a list with elements of type object. + XListType *string + + // x-kubernetes-map-type annotates an object to further describe its topology. + // This extension must only be used when type is object and may have 2 possible values: + // + // 1) `granular`: + // These maps are actual maps (key-value pairs) and each fields are independent + // from each other (they can each be manipulated by separate actors). This is + // the default behaviour for all maps. + // 2) `atomic`: the list is treated as a single entity, like a scalar. + // Atomic maps will be entirely replaced when updated. + // +optional + XMapType *string + + // x-kubernetes-validations -kubernetes-validations describes a list of validation rules written in the CEL expression language. + // This field is an alpha-level. Using this field requires the feature gate `CustomResourceValidationExpressions` to be enabled. + // +patchMergeKey=rule + // +patchStrategy=merge + // +listType=map + // +listMapKey=rule + XValidations ValidationRules +} + +// ValidationRules describes a list of validation rules written in the CEL expression language. +type ValidationRules []ValidationRule + +// ValidationRule describes a validation rule written in the CEL expression language. +type ValidationRule struct { + // Rule represents the expression which will be evaluated by CEL. + // ref: https://github.com/google/cel-spec + // The Rule is scoped to the location of the x-kubernetes-validations extension in the schema. + // The `self` variable in the CEL expression is bound to the scoped value. + // Example: + // - Rule scoped to the root of a resource with a status subresource: {"rule": "self.status.actual <= self.spec.maxDesired"} + // + // If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable + // via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as + // absent fields in CEL expressions. + // If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map + // are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map + // are accessible via CEL macros and functions such as `self.all(...)`. + // If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and + // functions. + // If the Rule is scoped to a scalar, `self` is bound to the scalar value. + // Examples: + // - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"} + // - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"} + // - Rule scoped to a string value: {"rule": "self.startsWith('kube')"} + // + // The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the + // object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible. + // + // Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL + // expressions. This includes: + // - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields. + // - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as: + // - A schema with no type and x-kubernetes-preserve-unknown-fields set to true + // - An array where the items schema is of an "unknown type" + // - An object where the additionalProperties schema is of an "unknown type" + // + // Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. + // Accessible property names are escaped according to the following rules when accessed in the expression: + // - '__' escapes to '__underscores__' + // - '.' escapes to '__dot__' + // - '-' escapes to '__dash__' + // - '/' escapes to '__slash__' + // - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are: + // "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if", + // "import", "let", "loop", "package", "namespace", "return". + // Examples: + // - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"} + // - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"} + // - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"} + // + // Equality on arrays with x-kubernetes-list-type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. + // Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type: + // - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and + // non-intersecting elements in `Y` are appended, retaining their partial order. + // - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values + // are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with + // non-intersecting keys are appended, retaining their partial order. + Rule string + // Message represents the message displayed when validation fails. The message is required if the Rule contains + // line breaks. The message must not contain line breaks. + // If unset, the message is "failed rule: {Rule}". + // e.g. "must be a URL with the host matching spec.host" + Message string + // MessageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. + // Since messageExpression is used as a failure message, it must evaluate to a string. + // If both message and messageExpression are present on a rule, then messageExpression will be used if validation + // fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced + // as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string + // that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and + // the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged. + // messageExpression has access to all the same variables as the rule; the only difference is the return type. + // Example: + // "x must be less than max ("+string(self.max)+")" + // +optional + MessageExpression string + // reason provides a machine-readable validation failure reason that is returned to the caller when a request fails this validation rule. + // The HTTP status code returned to the caller will match the reason of the reason of the first failed validation rule. + // The currently supported reasons are: "FieldValueInvalid", "FieldValueForbidden", "FieldValueRequired", "FieldValueDuplicate". + // If not set, default to use "FieldValueInvalid". + // All future added reasons must be accepted by clients when reading this value and unknown reasons should be treated as FieldValueInvalid. + // +optional + Reason *FieldValueErrorReason + // fieldPath represents the field path returned when the validation fails. + // It must be a relative JSON path (i.e. with array notation) scoped to the location of this x-kubernetes-validations extension in the schema and refer to an existing field. + // e.g. when validation checks if a specific attribute `foo` under a map `testMap`, the fieldPath could be set to `.testMap.foo` + // If the validation checks two lists must have unique attributes, the fieldPath could be set to either of the list: e.g. `.testList` + // It does not support list numeric index. + // It supports child operation to refer to an existing field currently. Refer to [JSONPath support in Kubernetes](https://kubernetes.io/docs/reference/kubectl/jsonpath/) for more info. + // Numeric index of array is not supported. + // For field name which contains special characters, use `['specialName']` to refer the field name. + // e.g. for attribute `foo.34$` appears in a list `testList`, the fieldPath could be set to `.testList['foo.34$']` + // +optional + FieldPath string +} + +// JSON represents any valid JSON value. +// These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. +type JSON interface{} + +// JSONSchemaURL represents a schema url. +type JSONSchemaURL string + +// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps +// or an array of JSONSchemaProps. Mainly here for serialization purposes. +type JSONSchemaPropsOrArray struct { + Schema *JSONSchemaProps + JSONSchemas []JSONSchemaProps +} + +// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value. +// Defaults to true for the boolean property. +type JSONSchemaPropsOrBool struct { + Allows bool + Schema *JSONSchemaProps +} + +// JSONSchemaDependencies represent a dependencies property. +type JSONSchemaDependencies map[string]JSONSchemaPropsOrStringArray + +// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array. +type JSONSchemaPropsOrStringArray struct { + Schema *JSONSchemaProps + Property []string +} + +// JSONSchemaDefinitions contains the models explicitly defined in this spec. +type JSONSchemaDefinitions map[string]JSONSchemaProps + +// ExternalDocumentation allows referencing an external resource for extended documentation. +type ExternalDocumentation struct { + Description string + URL string +} diff --git a/encoding/crd/k8s/metav1/time.go b/encoding/crd/k8s/metav1/time.go new file mode 100644 index 00000000000..4125bac4af7 --- /dev/null +++ b/encoding/crd/k8s/metav1/time.go @@ -0,0 +1,182 @@ +/* +Copyright 2014 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 metav1 + +import ( + "encoding/json" + "time" +) + +// Time is a wrapper around time.Time which supports correct +// marshaling to YAML and JSON. Wrappers are provided for many +// of the factory methods that the time package offers. +// +// +protobuf.options.marshal=false +// +protobuf.as=Timestamp +// +protobuf.options.(gogoproto.goproto_stringer)=false +type Time struct { + time.Time `protobuf:"-"` +} + +// DeepCopyInto creates a deep-copy of the Time value. The underlying time.Time +// type is effectively immutable in the time API, so it is safe to +// copy-by-assign, despite the presence of (unexported) Pointer fields. +func (t *Time) DeepCopyInto(out *Time) { + *out = *t +} + +// NewTime returns a wrapped instance of the provided time +func NewTime(time time.Time) Time { + return Time{time} +} + +// Date returns the Time corresponding to the supplied parameters +// by wrapping time.Date. +func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { + return Time{time.Date(year, month, day, hour, min, sec, nsec, loc)} +} + +// Now returns the current local time. +func Now() Time { + return Time{time.Now()} +} + +// IsZero returns true if the value is nil or time is zero. +func (t *Time) IsZero() bool { + if t == nil { + return true + } + return t.Time.IsZero() +} + +// Before reports whether the time instant t is before u. +func (t *Time) Before(u *Time) bool { + if t != nil && u != nil { + return t.Time.Before(u.Time) + } + return false +} + +// Equal reports whether the time instant t is equal to u. +func (t *Time) Equal(u *Time) bool { + if t == nil && u == nil { + return true + } + if t != nil && u != nil { + return t.Time.Equal(u.Time) + } + return false +} + +// Unix returns the local time corresponding to the given Unix time +// by wrapping time.Unix. +func Unix(sec int64, nsec int64) Time { + return Time{time.Unix(sec, nsec)} +} + +// Rfc3339Copy returns a copy of the Time at second-level precision. +func (t Time) Rfc3339Copy() Time { + copied, _ := time.Parse(time.RFC3339, t.Format(time.RFC3339)) + return Time{copied} +} + +// UnmarshalJSON implements the json.Unmarshaller interface. +func (t *Time) UnmarshalJSON(b []byte) error { + if len(b) == 4 && string(b) == "null" { + t.Time = time.Time{} + return nil + } + + var str string + err := json.Unmarshal(b, &str) + if err != nil { + return err + } + + pt, err := time.Parse(time.RFC3339, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// UnmarshalQueryParameter converts from a URL query parameter value to an object +func (t *Time) UnmarshalQueryParameter(str string) error { + if len(str) == 0 { + t.Time = time.Time{} + return nil + } + // Tolerate requests from older clients that used JSON serialization to build query params + if len(str) == 4 && str == "null" { + t.Time = time.Time{} + return nil + } + + pt, err := time.Parse(time.RFC3339, str) + if err != nil { + return err + } + + t.Time = pt.Local() + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (t Time) MarshalJSON() ([]byte, error) { + if t.IsZero() { + // Encode unset/nil objects as JSON's "null". + return []byte("null"), nil + } + buf := make([]byte, 0, len(time.RFC3339)+2) + buf = append(buf, '"') + // time cannot contain non escapable JSON characters + buf = t.UTC().AppendFormat(buf, time.RFC3339) + buf = append(buf, '"') + return buf, nil +} + +// ToUnstructured implements the value.UnstructuredConverter interface. +func (t Time) ToUnstructured() interface{} { + if t.IsZero() { + return nil + } + buf := make([]byte, 0, len(time.RFC3339)) + buf = t.UTC().AppendFormat(buf, time.RFC3339) + return string(buf) +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ Time) OpenAPISchemaType() []string { return []string{"string"} } + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ Time) OpenAPISchemaFormat() string { return "date-time" } + +// MarshalQueryParameter converts to a URL query parameter value +func (t Time) MarshalQueryParameter() (string, error) { + if t.IsZero() { + // Encode unset/nil objects as an empty string + return "", nil + } + + return t.UTC().Format(time.RFC3339), nil +} diff --git a/encoding/crd/k8s/metav1/types.go b/encoding/crd/k8s/metav1/types.go new file mode 100644 index 00000000000..adc0d172aa5 --- /dev/null +++ b/encoding/crd/k8s/metav1/types.go @@ -0,0 +1,1480 @@ +/* +Copyright 2015 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 metav1 contains API types that are common to all versions. +// +// The package contains two categories of types: +// - external (serialized) types that lack their own version (e.g TypeMeta) +// - internal (never-serialized) types that are needed by several different +// api groups, and so live here, to avoid duplication and/or import loops +// (e.g. LabelSelector). +// +// In the future, we will probably move these categories of objects into +// separate packages. +package metav1 + +import ( + "fmt" + "strings" +) + +// TypeMeta describes an individual object in an API response or request +// with strings representing the type of the object and its API schema version. +// Structures that are versioned or persisted should inline TypeMeta. +// +// +k8s:deepcopy-gen=false +type TypeMeta struct { + // Kind is a string value representing the REST resource this object represents. + // Servers may infer this from the endpoint the client submits requests to. + // Cannot be updated. + // In CamelCase. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` + + // APIVersion defines the versioned schema of this representation of an object. + // Servers should convert recognized schemas to the latest internal value, and + // may reject unrecognized values. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + // +optional + APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"` +} + +// ListMeta describes metadata that synthetic resources must have, including lists and +// various status objects. A resource may have only one of {ObjectMeta, ListMeta}. +type ListMeta struct { + // Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. + // +optional + SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,1,opt,name=selfLink"` + + // String that identifies the server's internal version of this object that + // can be used by clients to determine when objects have changed. + // Value must be treated as opaque by clients and passed unmodified back to the server. + // Populated by the system. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + // +optional + ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,2,opt,name=resourceVersion"` + + // continue may be set if the user set a limit on the number of items returned, and indicates that + // the server has more data available. The value is opaque and may be used to issue another request + // to the endpoint that served this list to retrieve the next set of available objects. Continuing a + // consistent list may not be possible if the server configuration has changed or more than a few + // minutes have passed. The resourceVersion field returned when using this continue value will be + // identical to the value in the first response, unless you have received this token from an error + // message. + Continue string `json:"continue,omitempty" protobuf:"bytes,3,opt,name=continue"` + + // remainingItemCount is the number of subsequent items in the list which are not included in this + // list response. If the list request contained label or field selectors, then the number of + // remaining items is unknown and the field will be left unset and omitted during serialization. + // If the list is complete (either because it is not chunking or because this is the last chunk), + // then there are no more remaining items and this field will be left unset and omitted during + // serialization. + // Servers older than v1.15 do not set this field. + // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients + // should not rely on the remainingItemCount to be set or to be exact. + // +optional + RemainingItemCount *int64 `json:"remainingItemCount,omitempty" protobuf:"bytes,4,opt,name=remainingItemCount"` +} + +// Field path constants that are specific to the internal API +// representation. +const ( + ObjectNameField = "metadata.name" +) + +// These are internal finalizer values for Kubernetes-like APIs, must be qualified name unless defined here +const ( + FinalizerOrphanDependents = "orphan" + FinalizerDeleteDependents = "foregroundDeletion" +) + +// ObjectMeta is metadata that all persisted resources must have, which includes all objects +// users must create. +type ObjectMeta struct { + // Name must be unique within a namespace. Is required when creating resources, although + // some resources may allow a client to request the generation of an appropriate name + // automatically. Name is primarily intended for creation idempotence and configuration + // definition. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names + // +optional + Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` + + // GenerateName is an optional prefix, used by the server, to generate a unique + // name ONLY IF the Name field has not been provided. + // If this field is used, the name returned to the client will be different + // than the name passed. This value will also be combined with a unique suffix. + // The provided value has the same validation rules as the Name field, + // and may be truncated by the length of the suffix required to make the value + // unique on the server. + // + // If this field is specified and the generated name exists, the server will return a 409. + // + // Applied only if Name is not specified. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency + // +optional + GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"` + + // Namespace defines the space within which each name must be unique. An empty namespace is + // equivalent to the "default" namespace, but "default" is the canonical representation. + // Not all objects are required to be scoped to a namespace - the value of this field for + // those objects will be empty. + // + // Must be a DNS_LABEL. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces + // +optional + Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` + + // Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. + // +optional + SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"` + + // UID is the unique in time and space value for this object. It is typically generated by + // the server on successful creation of a resource and is not allowed to change on PUT + // operations. + // + // Populated by the system. + // Read-only. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids + // +optional + UID string `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"` + + // An opaque value that represents the internal version of this object that can + // be used by clients to determine when objects have changed. May be used for optimistic + // concurrency, change detection, and the watch operation on a resource or set of resources. + // Clients must treat these values as opaque and passed unmodified back to the server. + // They may only be valid for a particular resource or set of resources. + // + // Populated by the system. + // Read-only. + // Value must be treated as opaque by clients and . + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + // +optional + ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"` + + // A sequence number representing a specific generation of the desired state. + // Populated by the system. Read-only. + // +optional + Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"` + + // CreationTimestamp is a timestamp representing the server time when this object was + // created. It is not guaranteed to be set in happens-before order across separate operations. + // Clients may not set this value. It is represented in RFC3339 form and is in UTC. + // + // Populated by the system. + // Read-only. + // Null for lists. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"` + + // DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This + // field is set by the server when a graceful deletion is requested by the user, and is not + // directly settable by a client. The resource is expected to be deleted (no longer visible + // from resource lists, and not reachable by name) after the time in this field, once the + // finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. + // Once the deletionTimestamp is set, this value may not be unset or be set further into the + // future, although it may be shortened or the resource may be deleted prior to this time. + // For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react + // by sending a graceful termination signal to the containers in the pod. After that 30 seconds, + // the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, + // remove the pod from the API. In the presence of network partitions, this object may still + // exist after this timestamp, until an administrator or automated process can determine the + // resource is fully terminated. + // If not set, graceful deletion of the object has not been requested. + // + // Populated by the system when a graceful deletion is requested. + // Read-only. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"` + + // Number of seconds allowed for this object to gracefully terminate before + // it will be removed from the system. Only set when deletionTimestamp is also set. + // May only be shortened. + // Read-only. + // +optional + DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"` + + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. May match selectors of replication controllers + // and services. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels + // +optional + Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"` + + // Annotations is an unstructured key value map stored with a resource that may be + // set by external tools to store and retrieve arbitrary metadata. They are not + // queryable and should be preserved when modifying objects. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations + // +optional + Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"` + + // List of objects depended by this object. If ALL objects in the list have + // been deleted, this object will be garbage collected. If this object is managed by a controller, + // then an entry in this list will point to this controller, with the controller field set to true. + // There cannot be more than one managing controller. + // +optional + // +patchMergeKey=uid + // +patchStrategy=merge + OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"` + + // Must be empty before the object is deleted from the registry. Each entry + // is an identifier for the responsible component that will remove the entry + // from the list. If the deletionTimestamp of the object is non-nil, entries + // in this list can only be removed. + // Finalizers may be processed and removed in any order. Order is NOT enforced + // because it introduces significant risk of stuck finalizers. + // finalizers is a shared field, any actor with permission can reorder it. + // If the finalizer list is processed in order, then this can lead to a situation + // in which the component responsible for the first finalizer in the list is + // waiting for a signal (field value, external system, or other) produced by a + // component responsible for a finalizer later in the list, resulting in a deadlock. + // Without enforced ordering finalizers are free to order amongst themselves and + // are not vulnerable to ordering changes in the list. + // +optional + // +patchStrategy=merge + Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"` + + // Tombstone: ClusterName was a legacy field that was always cleared by + // the system and never used. + // ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"` + + // ManagedFields maps workflow-id and version to the set of fields + // that are managed by that workflow. This is mostly for internal + // housekeeping, and users typically shouldn't need to set or + // understand this field. A workflow can be the user's name, a + // controller's name, or the name of a specific apply path like + // "ci-cd". The set of fields is always in the version that the + // workflow used when modifying the object. + // + // +optional + ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"` +} + +const ( + // NamespaceDefault means the object is in the default namespace which is applied when not specified by clients + NamespaceDefault = "default" + // NamespaceAll is the default argument to specify on a context when you want to list or filter resources across all namespaces + NamespaceAll = "" + // NamespaceNone is the argument for a context when there is no namespace. + NamespaceNone = "" + // NamespaceSystem is the system namespace where we place system components. + NamespaceSystem = "kube-system" + // NamespacePublic is the namespace where we place public info (ConfigMaps) + NamespacePublic = "kube-public" +) + +// OwnerReference contains enough information to let you identify an owning +// object. An owning object must be in the same namespace as the dependent, or +// be cluster-scoped, so there is no namespace field. +// +structType=atomic +type OwnerReference struct { + // API version of the referent. + APIVersion string `json:"apiVersion" protobuf:"bytes,5,opt,name=apiVersion"` + // Kind of the referent. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"` + // Name of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names + Name string `json:"name" protobuf:"bytes,3,opt,name=name"` + // UID of the referent. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids + UID string `json:"uid" protobuf:"bytes,4,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"` + // If true, this reference points to the managing controller. + // +optional + Controller *bool `json:"controller,omitempty" protobuf:"varint,6,opt,name=controller"` + // If true, AND if the owner has the "foregroundDeletion" finalizer, then + // the owner cannot be deleted from the key-value store until this + // reference is removed. + // See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion + // for how the garbage collector interacts with this field and enforces the foreground deletion. + // Defaults to false. + // To set this field, a user needs "delete" permission of the owner, + // otherwise 422 (Unprocessable Entity) will be returned. + // +optional + BlockOwnerDeletion *bool `json:"blockOwnerDeletion,omitempty" protobuf:"varint,7,opt,name=blockOwnerDeletion"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ListOptions is the query options to a standard REST list call. +type ListOptions struct { + TypeMeta `json:",inline"` + + // A selector to restrict the list of returned objects by their labels. + // Defaults to everything. + // +optional + LabelSelector string `json:"labelSelector,omitempty" protobuf:"bytes,1,opt,name=labelSelector"` + // A selector to restrict the list of returned objects by their fields. + // Defaults to everything. + // +optional + FieldSelector string `json:"fieldSelector,omitempty" protobuf:"bytes,2,opt,name=fieldSelector"` + + // +k8s:deprecated=includeUninitialized,protobuf=6 + + // Watch for changes to the described resources and return them as a stream of + // add, update, and remove notifications. Specify resourceVersion. + // +optional + Watch bool `json:"watch,omitempty" protobuf:"varint,3,opt,name=watch"` + // allowWatchBookmarks requests watch events with type "BOOKMARK". + // Servers that do not implement bookmarks may ignore this flag and + // bookmarks are sent at the server's discretion. Clients should not + // assume bookmarks are returned at any specific interval, nor may they + // assume the server will send any BOOKMARK event during a session. + // If this is not a watch, this field is ignored. + // +optional + AllowWatchBookmarks bool `json:"allowWatchBookmarks,omitempty" protobuf:"varint,9,opt,name=allowWatchBookmarks"` + + // resourceVersion sets a constraint on what resource versions a request may be served from. + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + // + // Defaults to unset + // +optional + ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,4,opt,name=resourceVersion"` + + // resourceVersionMatch determines how resourceVersion is applied to list calls. + // It is highly recommended that resourceVersionMatch be set for list calls where + // resourceVersion is set + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + // + // Defaults to unset + // +optional + ResourceVersionMatch ResourceVersionMatch `json:"resourceVersionMatch,omitempty" protobuf:"bytes,10,opt,name=resourceVersionMatch,casttype=ResourceVersionMatch"` + // Timeout for the list/watch call. + // This limits the duration of the call, regardless of any activity or inactivity. + // +optional + TimeoutSeconds *int64 `json:"timeoutSeconds,omitempty" protobuf:"varint,5,opt,name=timeoutSeconds"` + + // limit is a maximum number of responses to return for a list call. If more items exist, the + // server will set the `continue` field on the list metadata to a value that can be used with the + // same initial query to retrieve the next set of results. Setting a limit may return fewer than + // the requested amount of items (up to zero items) in the event all requested objects are + // filtered out and clients should only use the presence of the continue field to determine whether + // more results are available. Servers may choose not to support the limit argument and will return + // all of the available results. If limit is specified and the continue field is empty, clients may + // assume that no more results are available. This field is not supported if watch is true. + // + // The server guarantees that the objects returned when using continue will be identical to issuing + // a single list call without a limit - that is, no objects created, modified, or deleted after the + // first request is issued will be included in any subsequent continued requests. This is sometimes + // referred to as a consistent snapshot, and ensures that a client that is using limit to receive + // smaller chunks of a very large result can ensure they see all possible objects. If objects are + // updated during a chunked list the version of the object that was present at the time the first list + // result was calculated is returned. + Limit int64 `json:"limit,omitempty" protobuf:"varint,7,opt,name=limit"` + // The continue option should be set when retrieving more results from the server. Since this value is + // server defined, clients may only use the continue value from a previous query result with identical + // query parameters (except for the value of continue) and the server may reject a continue value it + // does not recognize. If the specified continue value is no longer valid whether due to expiration + // (generally five to fifteen minutes) or a configuration change on the server, the server will + // respond with a 410 ResourceExpired error together with a continue token. If the client needs a + // consistent list, it must restart their list without the continue field. Otherwise, the client may + // send another list request with the token received with the 410 error, the server will respond with + // a list starting from the next key, but from the latest snapshot, which is inconsistent from the + // previous list results - objects that are created, modified, or deleted after the first list request + // will be included in the response, as long as their keys are after the "next key". + // + // This field is not supported when watch is true. Clients may start a watch from the last + // resourceVersion value returned by the server and not miss any modifications. + Continue string `json:"continue,omitempty" protobuf:"bytes,8,opt,name=continue"` + + // `sendInitialEvents=true` may be set together with `watch=true`. + // In that case, the watch stream will begin with synthetic events to + // produce the current state of objects in the collection. Once all such + // events have been sent, a synthetic "Bookmark" event will be sent. + // The bookmark will report the ResourceVersion (RV) corresponding to the + // set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. + // Afterwards, the watch stream will proceed as usual, sending watch events + // corresponding to changes (subsequent to the RV) to objects watched. + // + // When `sendInitialEvents` option is set, we require `resourceVersionMatch` + // option to also be set. The semantic of the watch request is as following: + // - `resourceVersionMatch` = NotOlderThan + // is interpreted as "data at least as new as the provided `resourceVersion`" + // and the bookmark event is send when the state is synced + // to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + // If `resourceVersion` is unset, this is interpreted as "consistent read" and the + // bookmark event is send when the state is synced at least to the moment + // when request started being processed. + // - `resourceVersionMatch` set to any other value or unset + // Invalid error is returned. + // + // Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward + // compatibility reasons) and to false otherwise. + // +optional + SendInitialEvents *bool `json:"sendInitialEvents,omitempty" protobuf:"varint,11,opt,name=sendInitialEvents"` +} + +// resourceVersionMatch specifies how the resourceVersion parameter is applied. resourceVersionMatch +// may only be set if resourceVersion is also set. +// +// "NotOlderThan" matches data at least as new as the provided resourceVersion. +// "Exact" matches data at the exact resourceVersion provided. +// +// See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for +// details. +type ResourceVersionMatch string + +const ( + // ResourceVersionMatchNotOlderThan matches data at least as new as the provided + // resourceVersion. + ResourceVersionMatchNotOlderThan ResourceVersionMatch = "NotOlderThan" + // ResourceVersionMatchExact matches data at the exact resourceVersion + // provided. + ResourceVersionMatchExact ResourceVersionMatch = "Exact" +) + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// GetOptions is the standard query options to the standard REST get call. +type GetOptions struct { + TypeMeta `json:",inline"` + // resourceVersion sets a constraint on what resource versions a request may be served from. + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for + // details. + // + // Defaults to unset + // +optional + ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,1,opt,name=resourceVersion"` + // +k8s:deprecated=includeUninitialized,protobuf=2 +} + +// DeletionPropagation decides if a deletion will propagate to the dependents of +// the object, and how the garbage collector will handle the propagation. +type DeletionPropagation string + +const ( + // Orphans the dependents. + DeletePropagationOrphan DeletionPropagation = "Orphan" + // Deletes the object from the key-value store, the garbage collector will + // delete the dependents in the background. + DeletePropagationBackground DeletionPropagation = "Background" + // The object exists in the key-value store until the garbage collector + // deletes all the dependents whose ownerReference.blockOwnerDeletion=true + // from the key-value store. API sever will put the "foregroundDeletion" + // finalizer on the object, and sets its deletionTimestamp. This policy is + // cascading, i.e., the dependents will be deleted with Foreground. + DeletePropagationForeground DeletionPropagation = "Foreground" +) + +const ( + // DryRunAll means to complete all processing stages, but don't + // persist changes to storage. + DryRunAll = "All" +) + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// DeleteOptions may be provided when deleting an API object. +type DeleteOptions struct { + TypeMeta `json:",inline"` + + // The duration in seconds before the object should be deleted. Value must be non-negative integer. + // The value zero indicates delete immediately. If this value is nil, the default grace period for the + // specified type will be used. + // Defaults to a per object value if not specified. zero means delete immediately. + // +optional + GracePeriodSeconds *int64 `json:"gracePeriodSeconds,omitempty" protobuf:"varint,1,opt,name=gracePeriodSeconds"` + + // Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be + // returned. + // +k8s:conversion-gen=false + // +optional + Preconditions *Preconditions `json:"preconditions,omitempty" protobuf:"bytes,2,opt,name=preconditions"` + + // Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. + // Should the dependent objects be orphaned. If true/false, the "orphan" + // finalizer will be added to/removed from the object's finalizers list. + // Either this field or PropagationPolicy may be set, but not both. + // +optional + OrphanDependents *bool `json:"orphanDependents,omitempty" protobuf:"varint,3,opt,name=orphanDependents"` + + // Whether and how garbage collection will be performed. + // Either this field or OrphanDependents may be set, but not both. + // The default policy is decided by the existing finalizer set in the + // metadata.finalizers and the resource-specific default policy. + // Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - + // allow the garbage collector to delete the dependents in the background; + // 'Foreground' - a cascading policy that deletes all dependents in the + // foreground. + // +optional + PropagationPolicy *DeletionPropagation `json:"propagationPolicy,omitempty" protobuf:"varint,4,opt,name=propagationPolicy"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,5,rep,name=dryRun"` +} + +const ( + // FieldValidationIgnore ignores unknown/duplicate fields + FieldValidationIgnore = "Ignore" + // FieldValidationWarn responds with a warning, but successfully serve the request + FieldValidationWarn = "Warn" + // FieldValidationStrict fails the request on unknown/duplicate fields + FieldValidationStrict = "Strict" +) + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CreateOptions may be provided when creating an API object. +type CreateOptions struct { + TypeMeta `json:",inline"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"` + // +k8s:deprecated=includeUninitialized,protobuf=2 + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. + // +optional + FieldManager string `json:"fieldManager,omitempty" protobuf:"bytes,3,name=fieldManager"` + + // fieldValidation instructs the server on how to handle + // objects in the request (POST/PUT/PATCH) containing unknown + // or duplicate fields. Valid values are: + // - Ignore: This will ignore any unknown fields that are silently + // dropped from the object, and will ignore all but the last duplicate + // field that the decoder encounters. This is the default behavior + // prior to v1.23. + // - Warn: This will send a warning via the standard warning response + // header for each unknown field that is dropped from the object, and + // for each duplicate field that is encountered. The request will + // still succeed if there are no other errors, and will only persist + // the last of any duplicate fields. This is the default in v1.23+ + // - Strict: This will fail the request with a BadRequest error if + // any unknown fields would be dropped from the object, or if any + // duplicate fields are present. The error returned from the server + // will contain all unknown and duplicate fields encountered. + // +optional + FieldValidation string `json:"fieldValidation,omitempty" protobuf:"bytes,4,name=fieldValidation"` +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PatchOptions may be provided when patching an API object. +// PatchOptions is meant to be a superset of UpdateOptions. +type PatchOptions struct { + TypeMeta `json:",inline"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"` + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. Force + // flag must be unset for non-apply patch requests. + // +optional + Force *bool `json:"force,omitempty" protobuf:"varint,2,opt,name=force"` + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. This + // field is required for apply requests + // (application/apply-patch) but optional for non-apply patch + // types (JsonPatch, MergePatch, StrategicMergePatch). + // +optional + FieldManager string `json:"fieldManager,omitempty" protobuf:"bytes,3,name=fieldManager"` + + // fieldValidation instructs the server on how to handle + // objects in the request (POST/PUT/PATCH) containing unknown + // or duplicate fields. Valid values are: + // - Ignore: This will ignore any unknown fields that are silently + // dropped from the object, and will ignore all but the last duplicate + // field that the decoder encounters. This is the default behavior + // prior to v1.23. + // - Warn: This will send a warning via the standard warning response + // header for each unknown field that is dropped from the object, and + // for each duplicate field that is encountered. The request will + // still succeed if there are no other errors, and will only persist + // the last of any duplicate fields. This is the default in v1.23+ + // - Strict: This will fail the request with a BadRequest error if + // any unknown fields would be dropped from the object, or if any + // duplicate fields are present. The error returned from the server + // will contain all unknown and duplicate fields encountered. + // +optional + FieldValidation string `json:"fieldValidation,omitempty" protobuf:"bytes,4,name=fieldValidation"` +} + +// ApplyOptions may be provided when applying an API object. +// FieldManager is required for apply requests. +// ApplyOptions is equivalent to PatchOptions. It is provided as a convenience with documentation +// that speaks specifically to how the options fields relate to apply. +type ApplyOptions struct { + TypeMeta `json:",inline"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"` + + // Force is going to "force" Apply requests. It means user will + // re-acquire conflicting fields owned by other people. + Force bool `json:"force" protobuf:"varint,2,opt,name=force"` + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. This + // field is required. + FieldManager string `json:"fieldManager" protobuf:"bytes,3,name=fieldManager"` +} + +func (o ApplyOptions) ToPatchOptions() PatchOptions { + return PatchOptions{DryRun: o.DryRun, Force: &o.Force, FieldManager: o.FieldManager} +} + +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// UpdateOptions may be provided when updating an API object. +// All fields in UpdateOptions should also be present in PatchOptions. +type UpdateOptions struct { + TypeMeta `json:",inline"` + + // When present, indicates that modifications should not be + // persisted. An invalid or unrecognized dryRun directive will + // result in an error response and no further processing of the + // request. Valid values are: + // - All: all dry run stages will be processed + // +optional + DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"` + + // fieldManager is a name associated with the actor or entity + // that is making these changes. The value must be less than or + // 128 characters long, and only contain printable characters, + // as defined by https://golang.org/pkg/unicode/#IsPrint. + // +optional + FieldManager string `json:"fieldManager,omitempty" protobuf:"bytes,2,name=fieldManager"` + + // fieldValidation instructs the server on how to handle + // objects in the request (POST/PUT/PATCH) containing unknown + // or duplicate fields. Valid values are: + // - Ignore: This will ignore any unknown fields that are silently + // dropped from the object, and will ignore all but the last duplicate + // field that the decoder encounters. This is the default behavior + // prior to v1.23. + // - Warn: This will send a warning via the standard warning response + // header for each unknown field that is dropped from the object, and + // for each duplicate field that is encountered. The request will + // still succeed if there are no other errors, and will only persist + // the last of any duplicate fields. This is the default in v1.23+ + // - Strict: This will fail the request with a BadRequest error if + // any unknown fields would be dropped from the object, or if any + // duplicate fields are present. The error returned from the server + // will contain all unknown and duplicate fields encountered. + // +optional + FieldValidation string `json:"fieldValidation,omitempty" protobuf:"bytes,3,name=fieldValidation"` +} + +// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out. +type Preconditions struct { + // Specifies the target UID. + // +optional + UID *string `json:"uid,omitempty" protobuf:"bytes,1,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"` + // Specifies the target ResourceVersion + // +optional + ResourceVersion *string `json:"resourceVersion,omitempty" protobuf:"bytes,2,opt,name=resourceVersion"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Status is a return value for calls that don't return other objects. +type Status struct { + TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Status of the operation. + // One of: "Success" or "Failure". + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status string `json:"status,omitempty" protobuf:"bytes,2,opt,name=status"` + // A human-readable description of the status of this operation. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,3,opt,name=message"` + // A machine-readable description of why this operation is in the + // "Failure" status. If this value is empty there + // is no information available. A Reason clarifies an HTTP status + // code but does not override it. + // +optional + Reason StatusReason `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason,casttype=StatusReason"` + // Extended data associated with the reason. Each reason may define its + // own extended details. This field is optional and the data returned + // is not guaranteed to conform to any schema except that defined by + // the reason type. + // +optional + Details *StatusDetails `json:"details,omitempty" protobuf:"bytes,5,opt,name=details"` + // Suggested HTTP return code for this status, 0 if not set. + // +optional + Code int32 `json:"code,omitempty" protobuf:"varint,6,opt,name=code"` +} + +// StatusDetails is a set of additional properties that MAY be set by the +// server to provide additional information about a response. The Reason +// field of a Status object defines what attributes will be set. Clients +// must ignore fields that do not match the defined type of each attribute, +// and should assume that any attribute may be empty, invalid, or under +// defined. +type StatusDetails struct { + // The name attribute of the resource associated with the status StatusReason + // (when there is a single name which can be described). + // +optional + Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` + // The group attribute of the resource associated with the status StatusReason. + // +optional + Group string `json:"group,omitempty" protobuf:"bytes,2,opt,name=group"` + // The kind attribute of the resource associated with the status StatusReason. + // On some operations may differ from the requested resource Kind. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + Kind string `json:"kind,omitempty" protobuf:"bytes,3,opt,name=kind"` + // UID of the resource. + // (when there is a single resource which can be described). + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids + // +optional + UID string `json:"uid,omitempty" protobuf:"bytes,6,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"` + // The Causes array includes more details associated with the StatusReason + // failure. Not all StatusReasons may provide detailed causes. + // +optional + Causes []StatusCause `json:"causes,omitempty" protobuf:"bytes,4,rep,name=causes"` + // If specified, the time in seconds before the operation should be retried. Some errors may indicate + // the client must take an alternate action - for those errors this field may indicate how long to wait + // before taking the alternate action. + // +optional + RetryAfterSeconds int32 `json:"retryAfterSeconds,omitempty" protobuf:"varint,5,opt,name=retryAfterSeconds"` +} + +// Values of Status.Status +const ( + StatusSuccess = "Success" + StatusFailure = "Failure" +) + +// StatusReason is an enumeration of possible failure causes. Each StatusReason +// must map to a single HTTP status code, but multiple reasons may map +// to the same HTTP status code. +// TODO: move to apiserver +type StatusReason string + +const ( + // StatusReasonUnknown means the server has declined to indicate a specific reason. + // The details field may contain other information about this error. + // Status code 500. + StatusReasonUnknown StatusReason = "" + + // StatusReasonUnauthorized means the server can be reached and understood the request, but requires + // the user to present appropriate authorization credentials (identified by the WWW-Authenticate header) + // in order for the action to be completed. If the user has specified credentials on the request, the + // server considers them insufficient. + // Status code 401 + StatusReasonUnauthorized StatusReason = "Unauthorized" + + // StatusReasonForbidden means the server can be reached and understood the request, but refuses + // to take any further action. It is the result of the server being configured to deny access for some reason + // to the requested resource by the client. + // Details (optional): + // "kind" string - the kind attribute of the forbidden resource + // on some operations may differ from the requested + // resource. + // "id" string - the identifier of the forbidden resource + // Status code 403 + StatusReasonForbidden StatusReason = "Forbidden" + + // StatusReasonNotFound means one or more resources required for this operation + // could not be found. + // Details (optional): + // "kind" string - the kind attribute of the missing resource + // on some operations may differ from the requested + // resource. + // "id" string - the identifier of the missing resource + // Status code 404 + StatusReasonNotFound StatusReason = "NotFound" + + // StatusReasonAlreadyExists means the resource you are creating already exists. + // Details (optional): + // "kind" string - the kind attribute of the conflicting resource + // "id" string - the identifier of the conflicting resource + // Status code 409 + StatusReasonAlreadyExists StatusReason = "AlreadyExists" + + // StatusReasonConflict means the requested operation cannot be completed + // due to a conflict in the operation. The client may need to alter the + // request. Each resource may define custom details that indicate the + // nature of the conflict. + // Status code 409 + StatusReasonConflict StatusReason = "Conflict" + + // StatusReasonGone means the item is no longer available at the server and no + // forwarding address is known. + // Status code 410 + StatusReasonGone StatusReason = "Gone" + + // StatusReasonInvalid means the requested create or update operation cannot be + // completed due to invalid data provided as part of the request. The client may + // need to alter the request. When set, the client may use the StatusDetails + // message field as a summary of the issues encountered. + // Details (optional): + // "kind" string - the kind attribute of the invalid resource + // "id" string - the identifier of the invalid resource + // "causes" - one or more StatusCause entries indicating the data in the + // provided resource that was invalid. The code, message, and + // field attributes will be set. + // Status code 422 + StatusReasonInvalid StatusReason = "Invalid" + + // StatusReasonServerTimeout means the server can be reached and understood the request, + // but cannot complete the action in a reasonable time. The client should retry the request. + // This is may be due to temporary server load or a transient communication issue with + // another server. Status code 500 is used because the HTTP spec provides no suitable + // server-requested client retry and the 5xx class represents actionable errors. + // Details (optional): + // "kind" string - the kind attribute of the resource being acted on. + // "id" string - the operation that is being attempted. + // "retryAfterSeconds" int32 - the number of seconds before the operation should be retried + // Status code 500 + StatusReasonServerTimeout StatusReason = "ServerTimeout" + + // StatusReasonTimeout means that the request could not be completed within the given time. + // Clients can get this response only when they specified a timeout param in the request, + // or if the server cannot complete the operation within a reasonable amount of time. + // The request might succeed with an increased value of timeout param. The client *should* + // wait at least the number of seconds specified by the retryAfterSeconds field. + // Details (optional): + // "retryAfterSeconds" int32 - the number of seconds before the operation should be retried + // Status code 504 + StatusReasonTimeout StatusReason = "Timeout" + + // StatusReasonTooManyRequests means the server experienced too many requests within a + // given window and that the client must wait to perform the action again. A client may + // always retry the request that led to this error, although the client should wait at least + // the number of seconds specified by the retryAfterSeconds field. + // Details (optional): + // "retryAfterSeconds" int32 - the number of seconds before the operation should be retried + // Status code 429 + StatusReasonTooManyRequests StatusReason = "TooManyRequests" + + // StatusReasonBadRequest means that the request itself was invalid, because the request + // doesn't make any sense, for example deleting a read-only object. This is different than + // StatusReasonInvalid above which indicates that the API call could possibly succeed, but the + // data was invalid. API calls that return BadRequest can never succeed. + // Status code 400 + StatusReasonBadRequest StatusReason = "BadRequest" + + // StatusReasonMethodNotAllowed means that the action the client attempted to perform on the + // resource was not supported by the code - for instance, attempting to delete a resource that + // can only be created. API calls that return MethodNotAllowed can never succeed. + // Status code 405 + StatusReasonMethodNotAllowed StatusReason = "MethodNotAllowed" + + // StatusReasonNotAcceptable means that the accept types indicated by the client were not acceptable + // to the server - for instance, attempting to receive protobuf for a resource that supports only json and yaml. + // API calls that return NotAcceptable can never succeed. + // Status code 406 + StatusReasonNotAcceptable StatusReason = "NotAcceptable" + + // StatusReasonRequestEntityTooLarge means that the request entity is too large. + // Status code 413 + StatusReasonRequestEntityTooLarge StatusReason = "RequestEntityTooLarge" + + // StatusReasonUnsupportedMediaType means that the content type sent by the client is not acceptable + // to the server - for instance, attempting to send protobuf for a resource that supports only json and yaml. + // API calls that return UnsupportedMediaType can never succeed. + // Status code 415 + StatusReasonUnsupportedMediaType StatusReason = "UnsupportedMediaType" + + // StatusReasonInternalError indicates that an internal error occurred, it is unexpected + // and the outcome of the call is unknown. + // Details (optional): + // "causes" - The original error + // Status code 500 + StatusReasonInternalError StatusReason = "InternalError" + + // StatusReasonExpired indicates that the request is invalid because the content you are requesting + // has expired and is no longer available. It is typically associated with watches that can't be + // serviced. + // Status code 410 (gone) + StatusReasonExpired StatusReason = "Expired" + + // StatusReasonServiceUnavailable means that the request itself was valid, + // but the requested service is unavailable at this time. + // Retrying the request after some time might succeed. + // Status code 503 + StatusReasonServiceUnavailable StatusReason = "ServiceUnavailable" +) + +// StatusCause provides more information about an api.Status failure, including +// cases when multiple errors are encountered. +type StatusCause struct { + // A machine-readable description of the cause of the error. If this value is + // empty there is no information available. + // +optional + Type CauseType `json:"reason,omitempty" protobuf:"bytes,1,opt,name=reason,casttype=CauseType"` + // A human-readable description of the cause of the error. This field may be + // presented as-is to a reader. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"` + // The field of the resource that has caused this error, as named by its JSON + // serialization. May include dot and postfix notation for nested attributes. + // Arrays are zero-indexed. Fields may appear more than once in an array of + // causes due to fields having multiple errors. + // Optional. + // + // Examples: + // "name" - the field "name" on the current resource + // "items[0].name" - the field "name" on the first array entry in "items" + // +optional + Field string `json:"field,omitempty" protobuf:"bytes,3,opt,name=field"` +} + +// CauseType is a machine readable value providing more detail about what +// occurred in a status response. An operation may have multiple causes for a +// status (whether Failure or Success). +type CauseType string + +const ( + // CauseTypeFieldValueNotFound is used to report failure to find a requested value + // (e.g. looking up an ID). + CauseTypeFieldValueNotFound CauseType = "FieldValueNotFound" + // CauseTypeFieldValueRequired is used to report required values that are not + // provided (e.g. empty strings, null values, or empty arrays). + CauseTypeFieldValueRequired CauseType = "FieldValueRequired" + // CauseTypeFieldValueDuplicate is used to report collisions of values that must be + // unique (e.g. unique IDs). + CauseTypeFieldValueDuplicate CauseType = "FieldValueDuplicate" + // CauseTypeFieldValueInvalid is used to report malformed values (e.g. failed regex + // match). + CauseTypeFieldValueInvalid CauseType = "FieldValueInvalid" + // CauseTypeFieldValueNotSupported is used to report valid (as per formatting rules) + // values that can not be handled (e.g. an enumerated string). + CauseTypeFieldValueNotSupported CauseType = "FieldValueNotSupported" + // CauseTypeForbidden is used to report valid (as per formatting rules) + // values which would be accepted under some conditions, but which are not + // permitted by the current conditions (such as security policy). See + // Forbidden(). + CauseTypeForbidden CauseType = "FieldValueForbidden" + // CauseTypeTooLong is used to report that the given value is too long. + // This is similar to ErrorTypeInvalid, but the error will not include the + // too-long value. See TooLong(). + CauseTypeTooLong CauseType = "FieldValueTooLong" + // CauseTypeTooMany is used to report "too many". This is used to + // report that a given list has too many items. This is similar to FieldValueTooLong, + // but the error indicates quantity instead of length. + CauseTypeTooMany CauseType = "FieldValueTooMany" + // CauseTypeInternal is used to report other errors that are not related + // to user input. See InternalError(). + CauseTypeInternal CauseType = "InternalError" + // CauseTypeTypeInvalid is for the value did not match the schema type for that field + CauseTypeTypeInvalid CauseType = "FieldValueTypeInvalid" + // CauseTypeUnexpectedServerResponse is used to report when the server responded to the client + // without the expected return type. The presence of this cause indicates the error may be + // due to an intervening proxy or the server software malfunctioning. + CauseTypeUnexpectedServerResponse CauseType = "UnexpectedServerResponse" + // FieldManagerConflict is used to report when another client claims to manage this field, + // It should only be returned for a request using server-side apply. + CauseTypeFieldManagerConflict CauseType = "FieldManagerConflict" + // CauseTypeResourceVersionTooLarge is used to report that the requested resource version + // is newer than the data observed by the API server, so the request cannot be served. + CauseTypeResourceVersionTooLarge CauseType = "ResourceVersionTooLarge" +) + +// APIVersions lists the versions that are available, to allow clients to +// discover the API at /api, which is the root path of the legacy v1 API. +// +// +protobuf.options.(gogoproto.goproto_stringer)=false +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type APIVersions struct { + TypeMeta `json:",inline"` + // versions are the api versions that are available. + Versions []string `json:"versions" protobuf:"bytes,1,rep,name=versions"` + // a map of client CIDR to server address that is serving this group. + // This is to help clients reach servers in the most network-efficient way possible. + // Clients can use the appropriate server address as per the CIDR that they match. + // In case of multiple matches, clients should use the longest matching CIDR. + // The server returns only those CIDRs that it thinks that the client can match. + // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. + // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. + ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" protobuf:"bytes,2,rep,name=serverAddressByClientCIDRs"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// APIGroupList is a list of APIGroup, to allow clients to discover the API at +// /apis. +type APIGroupList struct { + TypeMeta `json:",inline"` + // groups is a list of APIGroup. + Groups []APIGroup `json:"groups" protobuf:"bytes,1,rep,name=groups"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// APIGroup contains the name, the supported versions, and the preferred version +// of a group. +type APIGroup struct { + TypeMeta `json:",inline"` + // name is the name of the group. + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + // versions are the versions supported in this group. + Versions []GroupVersionForDiscovery `json:"versions" protobuf:"bytes,2,rep,name=versions"` + // preferredVersion is the version preferred by the API server, which + // probably is the storage version. + // +optional + PreferredVersion GroupVersionForDiscovery `json:"preferredVersion,omitempty" protobuf:"bytes,3,opt,name=preferredVersion"` + // a map of client CIDR to server address that is serving this group. + // This is to help clients reach servers in the most network-efficient way possible. + // Clients can use the appropriate server address as per the CIDR that they match. + // In case of multiple matches, clients should use the longest matching CIDR. + // The server returns only those CIDRs that it thinks that the client can match. + // For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. + // Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. + // +optional + ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs,omitempty" protobuf:"bytes,4,rep,name=serverAddressByClientCIDRs"` +} + +// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match. +type ServerAddressByClientCIDR struct { + // The CIDR with which clients can match their IP to figure out the server address that they should use. + ClientCIDR string `json:"clientCIDR" protobuf:"bytes,1,opt,name=clientCIDR"` + // Address of this server, suitable for a client that matches the above CIDR. + // This can be a hostname, hostname:port, IP or IP:port. + ServerAddress string `json:"serverAddress" protobuf:"bytes,2,opt,name=serverAddress"` +} + +// GroupVersion contains the "group/version" and "version" string of a version. +// It is made a struct to keep extensibility. +type GroupVersionForDiscovery struct { + // groupVersion specifies the API group and version in the form "group/version" + GroupVersion string `json:"groupVersion" protobuf:"bytes,1,opt,name=groupVersion"` + // version specifies the version in the form of "version". This is to save + // the clients the trouble of splitting the GroupVersion. + Version string `json:"version" protobuf:"bytes,2,opt,name=version"` +} + +// APIResource specifies the name of a resource and whether it is namespaced. +type APIResource struct { + // name is the plural name of the resource. + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + // singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. + // The singularName is more correct for reporting status on a single item and both singular and plural are allowed + // from the kubectl CLI interface. + SingularName string `json:"singularName" protobuf:"bytes,6,opt,name=singularName"` + // namespaced indicates if a resource is namespaced or not. + Namespaced bool `json:"namespaced" protobuf:"varint,2,opt,name=namespaced"` + // group is the preferred group of the resource. Empty implies the group of the containing resource list. + // For subresources, this may have a different value, for example: Scale". + Group string `json:"group,omitempty" protobuf:"bytes,8,opt,name=group"` + // version is the preferred version of the resource. Empty implies the version of the containing resource list + // For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". + Version string `json:"version,omitempty" protobuf:"bytes,9,opt,name=version"` + // kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') + Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"` + // verbs is a list of supported kube verbs (this includes get, list, watch, create, + // update, patch, delete, deletecollection, and proxy) + Verbs Verbs `json:"verbs" protobuf:"bytes,4,opt,name=verbs"` + // shortNames is a list of suggested short names of the resource. + ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,5,rep,name=shortNames"` + // categories is a list of the grouped resources this resource belongs to (e.g. 'all') + Categories []string `json:"categories,omitempty" protobuf:"bytes,7,rep,name=categories"` + // The hash value of the storage version, the version this resource is + // converted to when written to the data store. Value must be treated + // as opaque by clients. Only equality comparison on the value is valid. + // This is an alpha feature and may change or be removed in the future. + // The field is populated by the apiserver only if the + // StorageVersionHash feature gate is enabled. + // This field will remain optional even if it graduates. + // +optional + StorageVersionHash string `json:"storageVersionHash,omitempty" protobuf:"bytes,10,opt,name=storageVersionHash"` +} + +// Verbs masks the value so protobuf can generate +// +// +protobuf.nullable=true +// +protobuf.options.(gogoproto.goproto_stringer)=false +type Verbs []string + +func (vs Verbs) String() string { + return fmt.Sprintf("%v", []string(vs)) +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// APIResourceList is a list of APIResource, it is used to expose the name of the +// resources supported in a specific group and version, and if the resource +// is namespaced. +type APIResourceList struct { + TypeMeta `json:",inline"` + // groupVersion is the group and version this APIResourceList is for. + GroupVersion string `json:"groupVersion" protobuf:"bytes,1,opt,name=groupVersion"` + // resources contains the name of the resources and if they are namespaced. + APIResources []APIResource `json:"resources" protobuf:"bytes,2,rep,name=resources"` +} + +// RootPaths lists the paths available at root. +// For example: "/healthz", "/apis". +type RootPaths struct { + // paths are the paths available at root. + Paths []string `json:"paths" protobuf:"bytes,1,rep,name=paths"` +} + +// TODO: remove me when watch is refactored +func LabelSelectorQueryParam(version string) string { + return "labelSelector" +} + +// TODO: remove me when watch is refactored +func FieldSelectorQueryParam(version string) string { + return "fieldSelector" +} + +// String returns available api versions as a human-friendly version string. +func (apiVersions APIVersions) String() string { + return strings.Join(apiVersions.Versions, ",") +} + +func (apiVersions APIVersions) GoString() string { + return apiVersions.String() +} + +// Patch is provided to give a concrete name and type to the Kubernetes PATCH request body. +type Patch struct{} + +// Note: +// There are two different styles of label selectors used in versioned types: +// an older style which is represented as just a string in versioned types, and a +// newer style that is structured. LabelSelector is an internal representation for the +// latter style. + +// A label selector is a label query over a set of resources. The result of matchLabels and +// matchExpressions are ANDed. An empty label selector matches all objects. A null +// label selector matches no objects. +// +structType=atomic +type LabelSelector struct { + // matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + // map is equivalent to an element of matchExpressions, whose key field is "key", the + // operator is "In", and the values array contains only "value". The requirements are ANDed. + // +optional + MatchLabels map[string]string `json:"matchLabels,omitempty" protobuf:"bytes,1,rep,name=matchLabels"` + // matchExpressions is a list of label selector requirements. The requirements are ANDed. + // +optional + MatchExpressions []LabelSelectorRequirement `json:"matchExpressions,omitempty" protobuf:"bytes,2,rep,name=matchExpressions"` +} + +// A label selector requirement is a selector that contains values, a key, and an operator that +// relates the key and values. +type LabelSelectorRequirement struct { + // key is the label key that the selector applies to. + Key string `json:"key" protobuf:"bytes,1,opt,name=key"` + // operator represents a key's relationship to a set of values. + // Valid operators are In, NotIn, Exists and DoesNotExist. + Operator LabelSelectorOperator `json:"operator" protobuf:"bytes,2,opt,name=operator,casttype=LabelSelectorOperator"` + // values is an array of string values. If the operator is In or NotIn, + // the values array must be non-empty. If the operator is Exists or DoesNotExist, + // the values array must be empty. This array is replaced during a strategic + // merge patch. + // +optional + Values []string `json:"values,omitempty" protobuf:"bytes,3,rep,name=values"` +} + +// A label selector operator is the set of operators that can be used in a selector requirement. +type LabelSelectorOperator string + +const ( + LabelSelectorOpIn LabelSelectorOperator = "In" + LabelSelectorOpNotIn LabelSelectorOperator = "NotIn" + LabelSelectorOpExists LabelSelectorOperator = "Exists" + LabelSelectorOpDoesNotExist LabelSelectorOperator = "DoesNotExist" +) + +// ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource +// that the fieldset applies to. +type ManagedFieldsEntry struct { + // Manager is an identifier of the workflow managing these fields. + Manager string `json:"manager,omitempty" protobuf:"bytes,1,opt,name=manager"` + // Operation is the type of operation which lead to this ManagedFieldsEntry being created. + // The only valid values for this field are 'Apply' and 'Update'. + Operation ManagedFieldsOperationType `json:"operation,omitempty" protobuf:"bytes,2,opt,name=operation,casttype=ManagedFieldsOperationType"` + // APIVersion defines the version of this resource that this field set + // applies to. The format is "group/version" just like the top-level + // APIVersion field. It is necessary to track the version of a field + // set because it cannot be automatically converted. + APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,3,opt,name=apiVersion"` + // Time is the timestamp of when the ManagedFields entry was added. The + // timestamp will also be updated if a field is added, the manager + // changes any of the owned fields value or removes a field. The + // timestamp does not update when a field is removed from the entry + // because another manager took it over. + // +optional + Time *Time `json:"time,omitempty" protobuf:"bytes,4,opt,name=time"` + + // Fields is tombstoned to show why 5 is a reserved protobuf tag. + //Fields *Fields `json:"fields,omitempty" protobuf:"bytes,5,opt,name=fields,casttype=Fields"` + + // FieldsType is the discriminator for the different fields format and version. + // There is currently only one possible value: "FieldsV1" + FieldsType string `json:"fieldsType,omitempty" protobuf:"bytes,6,opt,name=fieldsType"` + // FieldsV1 holds the first JSON version format as described in the "FieldsV1" type. + // +optional + FieldsV1 *FieldsV1 `json:"fieldsV1,omitempty" protobuf:"bytes,7,opt,name=fieldsV1"` + + // Subresource is the name of the subresource used to update that object, or + // empty string if the object was updated through the main resource. The + // value of this field is used to distinguish between managers, even if they + // share the same name. For example, a status update will be distinct from a + // regular update using the same manager name. + // Note that the APIVersion field is not related to the Subresource field and + // it always corresponds to the version of the main resource. + Subresource string `json:"subresource,omitempty" protobuf:"bytes,8,opt,name=subresource"` +} + +// ManagedFieldsOperationType is the type of operation which lead to a ManagedFieldsEntry being created. +type ManagedFieldsOperationType string + +const ( + ManagedFieldsOperationApply ManagedFieldsOperationType = "Apply" + ManagedFieldsOperationUpdate ManagedFieldsOperationType = "Update" +) + +// FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format. +// +// Each key is either a '.' representing the field itself, and will always map to an empty set, +// or a string representing a sub-field or item. The string will follow one of these four formats: +// 'f:', where is the name of a field in a struct, or key in a map +// 'v:', where is the exact json formatted value of a list item +// 'i:', where is position of a item in a list +// 'k:', where is a map of a list item's key fields to their unique values +// If a key maps to an empty Fields value, the field that key represents is part of the set. +// +// The exact format is defined in sigs.k8s.io/structured-merge-diff +// +protobuf.options.(gogoproto.goproto_stringer)=false +type FieldsV1 struct { + // Raw is the underlying serialization of this object. + Raw []byte `json:"-" protobuf:"bytes,1,opt,name=Raw"` +} + +func (f FieldsV1) String() string { + return string(f.Raw) +} + +// TableColumnDefinition contains information about a column returned in the Table. +// +protobuf=false +type TableColumnDefinition struct { + // name is a human readable name for the column. + Name string `json:"name"` + // type is an OpenAPI type definition for this column, such as number, integer, string, or + // array. + // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. + Type string `json:"type"` + // format is an optional OpenAPI type modifier for this column. A format modifies the type and + // imposes additional rules, like date or time formatting for a string. The 'name' format is applied + // to the primary identifier column which has type 'string' to assist in clients identifying column + // is the resource name. + // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. + Format string `json:"format"` + // description is a human readable description of this column. + Description string `json:"description"` + // priority is an integer defining the relative importance of this column compared to others. Lower + // numbers are considered higher priority. Columns that may be omitted in limited space scenarios + // should be given a higher priority. + Priority int32 `json:"priority"` +} + +// TableRowCondition allows a row to be marked with additional information. +// +protobuf=false +type TableRowCondition struct { + // Type of row condition. The only defined value is 'Completed' indicating that the + // object this row represents has reached a completed state and may be given less visual + // priority than other rows. Clients are not required to honor any conditions but should + // be consistent where possible about handling the conditions. + Type RowConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status ConditionStatus `json:"status"` + // (brief) machine readable reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` + // Human readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty"` +} + +type RowConditionType string + +// These are valid conditions of a row. This list is not exhaustive and new conditions may be +// included by other resources. +const ( + // RowCompleted means the underlying resource has reached completion and may be given less + // visual priority than other resources. + RowCompleted RowConditionType = "Completed" +) + +type ConditionStatus string + +// These are valid condition statuses. "ConditionTrue" means a resource is in the condition. +// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes +// can't decide if a resource is in the condition or not. In the future, we could add other +// intermediate conditions, e.g. ConditionDegraded. +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" + ConditionUnknown ConditionStatus = "Unknown" +) + +// IncludeObjectPolicy controls which portion of the object is returned with a Table. +type IncludeObjectPolicy string + +const ( + // IncludeNone returns no object. + IncludeNone IncludeObjectPolicy = "None" + // IncludeMetadata serializes the object containing only its metadata field. + IncludeMetadata IncludeObjectPolicy = "Metadata" + // IncludeObject contains the full object. + IncludeObject IncludeObjectPolicy = "Object" +) + +// TableOptions are used when a Table is requested by the caller. +// +k8s:conversion-gen:explicit-from=net/url.Values +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type TableOptions struct { + TypeMeta `json:",inline"` + + // NoHeaders is only exposed for internal callers. It is not included in our OpenAPI definitions + // and may be removed as a field in a future release. + NoHeaders bool `json:"-"` + + // includeObject decides whether to include each object along with its columnar information. + // Specifying "None" will return no object, specifying "Object" will return the full object contents, and + // specifying "Metadata" (the default) will return the object's metadata in the PartialObjectMetadata kind + // in version v1beta1 of the meta.k8s.io API group. + IncludeObject IncludeObjectPolicy `json:"includeObject,omitempty" protobuf:"bytes,1,opt,name=includeObject,casttype=IncludeObjectPolicy"` +} + +// PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients +// to get access to a particular ObjectMeta schema without knowing the details of the version. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type PartialObjectMetadata struct { + TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` +} + +// PartialObjectMetadataList contains a list of objects containing only their metadata +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type PartialObjectMetadataList struct { + TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // items contains each of the included items. + Items []PartialObjectMetadata `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// Condition contains details for one aspect of the current state of this API Resource. +// --- +// This struct is intended for direct use as an array at the field path .status.conditions. For example, +// +// type FooStatus struct{ +// // Represents the observations of a foo's current state. +// // Known .status.conditions.type are: "Available", "Progressing", and "Degraded" +// // +patchMergeKey=type +// // +patchStrategy=merge +// // +listType=map +// // +listMapKey=type +// Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` +// +// // other fields +// } +type Condition struct { + // type of condition in CamelCase or in foo.example.com/CamelCase. + // --- + // Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + // useful (see .node.status.conditions), the ability to deconflict is important. + // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$` + // +kubebuilder:validation:MaxLength=316 + Type string `json:"type" protobuf:"bytes,1,opt,name=type"` + // status of the condition, one of True, False, Unknown. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum=True;False;Unknown + Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status"` + // observedGeneration represents the .metadata.generation that the condition was set based upon. + // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + // with respect to the current state of the instance. + // +optional + // +kubebuilder:validation:Minimum=0 + ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` + // lastTransitionTime is the last time the condition transitioned from one status to another. + // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=date-time + LastTransitionTime Time `json:"lastTransitionTime" protobuf:"bytes,4,opt,name=lastTransitionTime"` + // reason contains a programmatic identifier indicating the reason for the condition's last transition. + // Producers of specific condition types may define expected values and meanings for this field, + // and whether the values are considered a guaranteed API. + // The value should be a CamelCase string. + // This field may not be empty. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=1024 + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` + Reason string `json:"reason" protobuf:"bytes,5,opt,name=reason"` + // message is a human readable message indicating details about the transition. + // This may be an empty string. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=32768 + Message string `json:"message" protobuf:"bytes,6,opt,name=message"` +} diff --git a/encoding/crd/unmarshal.go b/encoding/crd/unmarshal.go index aa5ea73b0f6..2a626bea215 100644 --- a/encoding/crd/unmarshal.go +++ b/encoding/crd/unmarshal.go @@ -20,16 +20,14 @@ import ( "os" "cuelang.org/go/cue" + "cuelang.org/go/encoding/crd/k8s/apiextensions" "cuelang.org/go/encoding/yaml" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" + goyaml "gopkg.in/yaml.v3" ) // Unmarshals a YAML file containing one or more CustomResourceDefinitions // into a list of CRD objects -func UnmarshalFile(ctx *cue.Context, filename string) ([]*v1.CustomResourceDefinition, error) { +func UnmarshalFile(ctx *cue.Context, filename string) ([]*apiextensions.CustomResourceDefinition, error) { data, err := os.ReadFile(filename) if err != nil { return nil, err @@ -45,7 +43,7 @@ func UnmarshalFile(ctx *cue.Context, filename string) ([]*v1.CustomResourceDefin return Unmarshal(crdv) } -func Unmarshal(crdv cue.Value) ([]*v1.CustomResourceDefinition, error) { +func Unmarshal(crdv cue.Value) ([]*apiextensions.CustomResourceDefinition, error) { var all []cue.Value switch crdv.IncompleteKind() { case cue.StructKind: @@ -60,7 +58,7 @@ func Unmarshal(crdv cue.Value) ([]*v1.CustomResourceDefinition, error) { } // Make return value list - ret := make([]*v1.CustomResourceDefinition, 0, len(all)) + ret := make([]*apiextensions.CustomResourceDefinition, 0, len(all)) // Iterate over each CRD for _, cueval := range all { @@ -77,7 +75,7 @@ func Unmarshal(crdv cue.Value) ([]*v1.CustomResourceDefinition, error) { // Unmarshals YAML data for a single containing one or more CustomResourceDefinitions // into a list of CRD objects -func UnmarshalOne(val cue.Value) (*v1.CustomResourceDefinition, error) { +func UnmarshalOne(val cue.Value) (*apiextensions.CustomResourceDefinition, error) { // Encode the CUE value as YAML bytes d, err := yaml.Encode(val) if err != nil { @@ -85,8 +83,9 @@ func UnmarshalOne(val cue.Value) (*v1.CustomResourceDefinition, error) { } // Decode into a v1.CustomResourceDefinition - obj := &v1.CustomResourceDefinition{} - if err = runtime.DecodeInto(serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder(), d, obj); err != nil { + obj := &apiextensions.CustomResourceDefinition{} + err = goyaml.Unmarshal(d, obj) + if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 74d5033ce4a..4c139b3d436 100644 --- a/go.mod +++ b/go.mod @@ -24,28 +24,13 @@ require ( golang.org/x/text v0.13.0 golang.org/x/tools v0.14.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/apiextensions-apiserver v0.28.3 - k8s.io/apimachinery v0.28.3 ) require ( - github.com/go-logr/logr v1.2.4 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gofuzz v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/kr/text v0.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/sys v0.13.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index d7d9eb6cdbd..a6b45587772 100644 --- a/go.sum +++ b/go.sum @@ -4,33 +4,18 @@ github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEa github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -42,11 +27,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -54,8 +34,6 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIsgmHpEOGbUs6VtHBXRR1OHevnj7hLx9ZcdNGW4= github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -66,72 +44,23 @@ github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/tetratelabs/wazero v1.0.2 h1:lpwL5zczFHk2mxKur98035Gig+Z3vd9JURk6lUdZxXY= github.com/tetratelabs/wazero v1.0.2/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= -k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= -k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= -k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From 46bc7aa4c05de375d26092a190727a8c2e584902 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Wed, 22 Nov 2023 14:27:23 -0500 Subject: [PATCH 15/20] rename files and tidy up interfaces Signed-off-by: Justen Stall --- cmd/cue/cmd/get_crd.go | 4 +- encoding/crd/{decode.go => convert.go} | 134 +++---------------------- encoding/crd/parse.go | 117 +++++++++++++++++++++ encoding/crd/unmarshal.go | 93 ----------------- 4 files changed, 132 insertions(+), 216 deletions(-) rename encoding/crd/{decode.go => convert.go} (79%) create mode 100644 encoding/crd/parse.go delete mode 100644 encoding/crd/unmarshal.go diff --git a/cmd/cue/cmd/get_crd.go b/cmd/cue/cmd/get_crd.go index 26b2bfa32ee..c72c78dcc73 100644 --- a/cmd/cue/cmd/get_crd.go +++ b/cmd/cue/cmd/get_crd.go @@ -61,14 +61,14 @@ CustomResourceDefinitions are converted to cue structs adhering to the following } func runGetCRD(cmd *Command, args []string) error { - decoder := crd.NewDecoder(cmd.ctx, "// cue get crd "+strings.Join(args, " ")) + decoder := crd.NewExtractor(cmd.ctx, "// cue get crd "+strings.Join(args, " ")) data, err := os.ReadFile(args[0]) if err != nil { return err } - crds, err := decoder.Decode(data) + crds, err := decoder.Instances(data) if err != nil { return err } diff --git a/encoding/crd/decode.go b/encoding/crd/convert.go similarity index 79% rename from encoding/crd/decode.go rename to encoding/crd/convert.go index b59bcb94fad..3c193c3219c 100644 --- a/encoding/crd/decode.go +++ b/encoding/crd/convert.go @@ -16,7 +16,6 @@ limitations under the License. package crd import ( - "encoding/json" "fmt" "path" "strings" @@ -28,30 +27,29 @@ import ( "cuelang.org/go/cue/token" "cuelang.org/go/encoding/crd/k8s/apiextensions" "cuelang.org/go/encoding/openapi" - "cuelang.org/go/encoding/yaml" ) -// Decoder generates CUE definitions from Kubernetes CRDs using the OpenAPI v3 spec. -type Decoder struct { +// Extractor generates CUE definitions from Kubernetes CRDs using the OpenAPI v3 spec. +type Extractor struct { ctx *cue.Context header string } -// NewDecoder creates an Importer for the given CUE context. -func NewDecoder(ctx *cue.Context, header string) *Decoder { - return &Decoder{ +// NewExtractor creates an Importer for the given CUE context. +func NewExtractor(ctx *cue.Context, header string) *Extractor { + return &Extractor{ ctx: ctx, header: header, } } -// Decode takes a multi-doc YAML containing Kubernetes CRDs and returns the CUE definitions +// Instances takes a multi-doc YAML containing Kubernetes CRDs and returns the CUE definitions // generated from the OpenAPI spec. The resulting key value pairs, contain a unique identifier // in the format `//` and the contents of the CUE definition. -func (imp *Decoder) Decode(crdData []byte) (map[string][]byte, error) { +func (b *Extractor) Instances(crdData []byte) (map[string][]byte, error) { result := make(map[string][]byte) - crds, err := imp.fromYAML(crdData) + crds, err := b.fromYAML(crdData) if err != nil { return result, err } @@ -63,7 +61,7 @@ func (imp *Decoder) Decode(crdData []byte) (map[string][]byte, error) { return result, err } name := path.Join(crd.Props.Spec.Group, crd.Props.Spec.Names.Singular, crdVersion.Version) - result[name] = []byte(fmt.Sprintf("%s\n\npackage %s\n\n%s", imp.header, crdVersion.Version, string(def))) + result[name] = []byte(fmt.Sprintf("%s\n\npackage %s\n\n%s", b.header, crdVersion.Version, string(def))) } } @@ -75,26 +73,11 @@ func (imp *Decoder) Decode(crdData []byte) (map[string][]byte, error) { // // This function preserves the ordering of schemas declared in the input YAML in // the resulting [IntermediateCRD.Schemas] field. -func (imp *Decoder) fromYAML(b []byte) ([]*IntermediateCRD, error) { - +func (b *Extractor) fromYAML(data []byte) ([]*IntermediateCRD, error) { // The filename provided here is only used in error messages - yf, err := yaml.Extract("crd.yaml", b) + all, err := splitFile(b.ctx, "crd.yaml", data) if err != nil { - return nil, fmt.Errorf("input is not valid yaml: %w", err) - } - crdv := imp.ctx.BuildFile(yf) - - var all []cue.Value - switch crdv.IncompleteKind() { - case cue.StructKind: - all = append(all, crdv) - case cue.ListKind: - iter, _ := crdv.List() - for iter.Next() { - all = append(all, iter.Value()) - } - default: - return nil, fmt.Errorf("input does not appear to be one or multiple CRDs: %s", crdv) + return nil, err } ret := make([]*IntermediateCRD, 0, len(all)) @@ -139,7 +122,7 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { } var err error - cc.Props, err = UnmarshalOne(crd) + cc.Props, err = parseCRD(crd) if err != nil { return nil, fmt.Errorf("error decoding crd props into Go struct: %w", err) } @@ -423,97 +406,6 @@ const ( XValidations XExtensionAttr = "validations" ) -// Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them -func xKubernetesAttributes(path []cue.Selector, prop apiextensions.JSONSchemaProps) []struct { - path []cue.Selector - attr *ast.Attribute -} { - extensions := []struct { - path []cue.Selector - attr *ast.Attribute - }{} - - // attrFields := []string{} - - attrBody := make([]string, 0) - addAttr := func(key XExtensionAttr, val string) { - attrBody = append(attrBody, fmt.Sprintf("%s=%s", key, val)) - } - - if prop.XPreserveUnknownFields != nil { - // attrFields = append(attrFields, fmt.Sprintf("%s=%t", XPreserveUnknownFields, *prop.XPreserveUnknownFields)) - addAttr(XPreserveUnknownFields, fmt.Sprintf("%t", *prop.XPreserveUnknownFields)) - } - - if prop.XEmbeddedResource { - // attrFields = append(attrFields, fmt.Sprintf("%s=%t", XEmbeddedResource, *prop.XPreserveUnknownFields)) - addAttr(XEmbeddedResource, fmt.Sprintf("%t", prop.XEmbeddedResource)) - } - - if prop.XIntOrString { - addAttr(XIntOrString, fmt.Sprintf("%t", prop.XIntOrString)) - } - - if len(prop.XListMapKeys) > 0 { - addAttr(XListMapKeys, fmt.Sprintf(`'["%s"]'`, strings.Join(prop.XListMapKeys, `", "`))) - } - - if prop.XListType != nil { - addAttr(XListType, fmt.Sprintf("%q", *prop.XListType)) - } - - if prop.XMapType != nil { - addAttr(XMapType, fmt.Sprintf("%q", *prop.XMapType)) - } - - if len(prop.XValidations) > 0 { - vals, err := json.Marshal(prop.XValidations) - if err != nil { - panic(err) - } - addAttr(XValidations, "\"\"\"\n"+string(vals)+"\n\"\"\"") - } - - if len(attrBody) > 0 { - attrText := fmt.Sprintf("@%s(%s)", "crd", strings.Join(attrBody, ", ")) - extensions = append(extensions, struct { - path []cue.Selector - attr *ast.Attribute - }{ - path: path, - attr: &ast.Attribute{Text: attrText}, - }) - - // fmt.Println(cue.MakePath(path...).String() + ": " + attrText) - } - - for nextPath := range prop.Properties { - // Recursively add subextensions for each property - subExts := xKubernetesAttributes(append(path, cue.Str(nextPath)), prop.Properties[nextPath]) - extensions = append(extensions, subExts...) - } - - // TODO: array does not work right, see https://github.com/istio/istio/blob/0d5f530188dfe571bf0d8f515618ba99a0dc3e6c/manifests/charts/base/crds/crd-all.gen.yaml#L188 - // if prop.Type == "array" { - // if len(prop.Items.JSONSchemas) > 0 { - // for i, _ := n.List(); i.Next(); { - // a = append(a, i.Value()) - // } - // // Iterate each schema in list and add attribute for that list index - // // for i, nextProp := range prop.Items.JSONSchemas { - // // // Add attribute to the item at index i - // // } - // } else { - // // Add attribute to the pattern constraint - // // // Recursively add subextensions for each property - // // subExts := xKubernetesAttributes(append(path, cue.AnyIndex), *prop.Items.Schema) - // // extensions = append(extensions, subExts...) - // } - // } - - return extensions -} - // Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them func mapAttributes(val cue.Value, prop apiextensions.JSONSchemaProps) cue.Value { attr := xk8sattr(*val.Context(), prop) diff --git a/encoding/crd/parse.go b/encoding/crd/parse.go new file mode 100644 index 00000000000..4af1098ad5c --- /dev/null +++ b/encoding/crd/parse.go @@ -0,0 +1,117 @@ +/* +Copyright 2023 Stefan Prodan + +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 crd + +import ( + "fmt" + + "cuelang.org/go/cue" + "cuelang.org/go/encoding/crd/k8s/apiextensions" + "cuelang.org/go/encoding/yaml" + goyaml "gopkg.in/yaml.v3" +) + +// Splits a YAML file containing one or more YAML documents into its elements +func splitFile(ctx *cue.Context, filename string, data []byte) ([]cue.Value, error) { + // The filename provided here is only used in error messages + yf, err := yaml.Extract(filename, data) + if err != nil { + return nil, fmt.Errorf("input is not valid yaml: %w", err) + } + + val := ctx.BuildFile(yf) + + var all []cue.Value + switch val.IncompleteKind() { + case cue.StructKind: + all = append(all, val) + case cue.ListKind: + iter, _ := val.List() + for iter.Next() { + all = append(all, iter.Value()) + } + default: + return nil, fmt.Errorf("input does not appear to be one or multiple YAML documents: %s", val) + } + + return all, nil +} + +// // Unmarshals a YAML file containing one or more CustomResourceDefinitions into a list of CRD objects +// func parseFile(ctx *cue.Context, filename string) ([]*apiextensions.CustomResourceDefinition, error) { +// data, err := os.ReadFile(filename) +// if err != nil { +// return nil, err +// } + +// // The filename provided here is only used in error messages +// yf, err := yaml.Extract(filename, data) +// if err != nil { +// return nil, fmt.Errorf("input is not valid yaml: %w", err) +// } + +// return parseMultiple(ctx.BuildFile(yf)) +// } + +// // Parses a cue.Value containing one or more CustomResourceDefinitions into a list of CRD objects +// func parseMultiple(val cue.Value) ([]*apiextensions.CustomResourceDefinition, error) { +// var all []cue.Value +// switch val.IncompleteKind() { +// case cue.StructKind: +// all = append(all, val) +// case cue.ListKind: +// iter, _ := val.List() +// for iter.Next() { +// all = append(all, iter.Value()) +// } +// default: +// return nil, fmt.Errorf("input does not appear to be one or multiple CRDs: %s", val) +// } + +// // Make return value list +// ret := make([]*apiextensions.CustomResourceDefinition, 0, len(all)) + +// // Iterate over each CRD +// for _, singleval := range all { +// obj, err := parseSingle(singleval) +// if err != nil { +// return ret, err +// } + +// ret = append(ret, obj) +// } + +// return ret, nil +// } + +// Unmarshals YAML data for a single containing one or more CustomResourceDefinitions +// into a list of CRD objects +func parseCRD(val cue.Value) (*apiextensions.CustomResourceDefinition, error) { + // Encode the CUE value as YAML bytes + d, err := yaml.Encode(val) + if err != nil { + return nil, err + } + + // Decode into a v1.CustomResourceDefinition + obj := &apiextensions.CustomResourceDefinition{} + err = goyaml.Unmarshal(d, obj) + if err != nil { + return nil, err + } + + return obj, nil +} diff --git a/encoding/crd/unmarshal.go b/encoding/crd/unmarshal.go deleted file mode 100644 index 2a626bea215..00000000000 --- a/encoding/crd/unmarshal.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -Copyright 2023 Stefan Prodan - -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 crd - -import ( - "fmt" - "os" - - "cuelang.org/go/cue" - "cuelang.org/go/encoding/crd/k8s/apiextensions" - "cuelang.org/go/encoding/yaml" - goyaml "gopkg.in/yaml.v3" -) - -// Unmarshals a YAML file containing one or more CustomResourceDefinitions -// into a list of CRD objects -func UnmarshalFile(ctx *cue.Context, filename string) ([]*apiextensions.CustomResourceDefinition, error) { - data, err := os.ReadFile(filename) - if err != nil { - return nil, err - } - - // The filename provided here is only used in error messages - yf, err := yaml.Extract(filename, data) - if err != nil { - return nil, fmt.Errorf("input is not valid yaml: %w", err) - } - crdv := ctx.BuildFile(yf) - - return Unmarshal(crdv) -} - -func Unmarshal(crdv cue.Value) ([]*apiextensions.CustomResourceDefinition, error) { - var all []cue.Value - switch crdv.IncompleteKind() { - case cue.StructKind: - all = append(all, crdv) - case cue.ListKind: - iter, _ := crdv.List() - for iter.Next() { - all = append(all, iter.Value()) - } - default: - return nil, fmt.Errorf("input does not appear to be one or multiple CRDs: %s", crdv) - } - - // Make return value list - ret := make([]*apiextensions.CustomResourceDefinition, 0, len(all)) - - // Iterate over each CRD - for _, cueval := range all { - obj, err := UnmarshalOne(cueval) - if err != nil { - return ret, err - } - - ret = append(ret, obj) - } - - return ret, nil -} - -// Unmarshals YAML data for a single containing one or more CustomResourceDefinitions -// into a list of CRD objects -func UnmarshalOne(val cue.Value) (*apiextensions.CustomResourceDefinition, error) { - // Encode the CUE value as YAML bytes - d, err := yaml.Encode(val) - if err != nil { - return nil, err - } - - // Decode into a v1.CustomResourceDefinition - obj := &apiextensions.CustomResourceDefinition{} - err = goyaml.Unmarshal(d, obj) - if err != nil { - return nil, err - } - - return obj, nil -} From a90f53d255c6da6240663b1129378c1de094f437 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Wed, 22 Nov 2023 14:44:21 -0500 Subject: [PATCH 16/20] remove unused Signed-off-by: Justen Stall --- encoding/crd/convert.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/encoding/crd/convert.go b/encoding/crd/convert.go index 3c193c3219c..372d63805ac 100644 --- a/encoding/crd/convert.go +++ b/encoding/crd/convert.go @@ -193,26 +193,7 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { // Add attributes for k8s oapi extensions // construct a map of all paths using x-kubernetes-* OpenAPI extensions - // TODO: what is the list type? sch = mapAttributes(sch, rootosch) - // for _, extAttr := range xKubernetesAttributes(defpath.Selectors(), rootosch) { - // attrPath := cue.MakePath(extAttr.path...) - // sch = sch.FillPath(attrPath, extAttr.attr) - // // extendedVal := sch.LookupPath(attrPath) - // // switch x := extendedVal.Source().(type) { - // // case *ast.StructLit: - // // x.Elts = append(x.Elts, extAttr.attr) - // // case *ast.Field: - // // x.Attrs = append(x.Attrs, extAttr.attr) - // // case *ast.File: - // // x.Decls = append(x.Decls, extAttr.attr) - // // default: - // // fmt.Println(attrPath) - // // fmt.Printf("extendedVal: %v\n\n", extendedVal) - // // // fmt.Println(reflect.TypeOf(node)) - // // // fmt.Printf("node: %v\n", node) - // // } - // } // now, go back to an AST because it's easier to manipulate references there var schast *ast.File From f3be861bfb52478e5378c9068e8fc7c7a2ce4346 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Wed, 22 Nov 2023 14:51:27 -0500 Subject: [PATCH 17/20] grab types with json tags Signed-off-by: Justen Stall --- encoding/crd/convert.go | 2 +- encoding/crd/k8s/apiextensions/types.go | 422 ----------------- encoding/crd/k8s/apiextensions/v1/types.go | 439 ++++++++++++++++++ .../{ => v1}/types_jsonschema.go | 219 ++++++--- encoding/crd/k8s/metav1/time.go | 2 +- encoding/crd/parse.go | 2 +- 6 files changed, 598 insertions(+), 488 deletions(-) delete mode 100644 encoding/crd/k8s/apiextensions/types.go create mode 100644 encoding/crd/k8s/apiextensions/v1/types.go rename encoding/crd/k8s/apiextensions/{ => v1}/types_jsonschema.go (53%) diff --git a/encoding/crd/convert.go b/encoding/crd/convert.go index 372d63805ac..3bcb56e40e7 100644 --- a/encoding/crd/convert.go +++ b/encoding/crd/convert.go @@ -25,7 +25,7 @@ import ( "cuelang.org/go/cue/ast/astutil" "cuelang.org/go/cue/format" "cuelang.org/go/cue/token" - "cuelang.org/go/encoding/crd/k8s/apiextensions" + apiextensions "cuelang.org/go/encoding/crd/k8s/apiextensions/v1" "cuelang.org/go/encoding/openapi" ) diff --git a/encoding/crd/k8s/apiextensions/types.go b/encoding/crd/k8s/apiextensions/types.go deleted file mode 100644 index aed482025af..00000000000 --- a/encoding/crd/k8s/apiextensions/types.go +++ /dev/null @@ -1,422 +0,0 @@ -/* -Copyright 2017 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 apiextensions - -import ( - metav1 "cuelang.org/go/encoding/crd/k8s/metav1" -) - -// ConversionStrategyType describes different conversion types. -type ConversionStrategyType string - -const ( - // NoneConverter is a converter that only sets apiversion of the CR and leave everything else unchanged. - NoneConverter ConversionStrategyType = "None" - // WebhookConverter is a converter that calls to an external webhook to convert the CR. - WebhookConverter ConversionStrategyType = "Webhook" -) - -// CustomResourceDefinitionSpec describes how a user wants their resource to appear -type CustomResourceDefinitionSpec struct { - // Group is the group this resource belongs in - Group string - // Version is the version this resource belongs in - // Should be always first item in Versions field if provided. - // Optional, but at least one of Version or Versions must be set. - // Deprecated: Please use `Versions`. - Version string - // Names are the names used to describe this custom resource - Names CustomResourceDefinitionNames - // Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced - Scope ResourceScope - // Validation describes the validation methods for CustomResources - // Optional, the global validation schema for all versions. - // Top-level and per-version schemas are mutually exclusive. - // +optional - Validation *CustomResourceValidation - // Subresources describes the subresources for CustomResource - // Optional, the global subresources for all versions. - // Top-level and per-version subresources are mutually exclusive. - // +optional - Subresources *CustomResourceSubresources - // Versions is the list of all supported versions for this resource. - // If Version field is provided, this field is optional. - // Validation: All versions must use the same validation schema for now. i.e., top - // level Validation field is applied to all of these versions. - // Order: The version name will be used to compute the order. - // If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered - // lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version), - // then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first - // by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing - // major version, then minor version. An example sorted list of versions: - // v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10. - Versions []CustomResourceDefinitionVersion - // AdditionalPrinterColumns are additional columns shown e.g. in kubectl next to the name. Defaults to a created-at column. - // Optional, the global columns for all versions. - // Top-level and per-version columns are mutually exclusive. - // +optional - AdditionalPrinterColumns []CustomResourceColumnDefinition - - // `conversion` defines conversion settings for the CRD. - Conversion *CustomResourceConversion - - // preserveUnknownFields disables pruning of object fields which are not - // specified in the OpenAPI schema. apiVersion, kind, metadata and known - // fields inside metadata are always preserved. - // Defaults to true in v1beta and will default to false in v1. - PreserveUnknownFields *bool -} - -// CustomResourceConversion describes how to convert different versions of a CR. -type CustomResourceConversion struct { - // `strategy` specifies the conversion strategy. Allowed values are: - // - `None`: The converter only change the apiVersion and would not touch any other field in the CR. - // - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information - // is needed for this option. This requires spec.preserveUnknownFields to be false. - Strategy ConversionStrategyType - - // `webhookClientConfig` is the instructions for how to call the webhook if strategy is `Webhook`. - WebhookClientConfig *WebhookClientConfig - - // ConversionReviewVersions is an ordered list of preferred `ConversionReview` - // versions the Webhook expects. API server will try to use first version in - // the list which it supports. If none of the versions specified in this list - // supported by API server, conversion will fail for this object. - // If a persisted Webhook configuration specifies allowed versions and does not - // include any versions known to the API Server, calls to the webhook will fail. - // +optional - ConversionReviewVersions []string -} - -// WebhookClientConfig contains the information to make a TLS -// connection with the webhook. It has the same field as admissionregistration.internal.WebhookClientConfig. -type WebhookClientConfig struct { - // `url` gives the location of the webhook, in standard URL form - // (`scheme://host:port/path`). Exactly one of `url` or `service` - // must be specified. - // - // The `host` should not refer to a service running in the cluster; use - // the `service` field instead. The host might be resolved via external - // DNS in some apiservers (e.g., `kube-apiserver` cannot resolve - // in-cluster DNS as that would be a layering violation). `host` may - // also be an IP address. - // - // Please note that using `localhost` or `127.0.0.1` as a `host` is - // risky unless you take great care to run this webhook on all hosts - // which run an apiserver which might need to make calls to this - // webhook. Such installs are likely to be non-portable, i.e., not easy - // to turn up in a new cluster. - // - // The scheme must be "https"; the URL must begin with "https://". - // - // A path is optional, and if present may be any string permissible in - // a URL. You may use the path to pass an arbitrary string to the - // webhook, for example, a cluster identifier. - // - // Attempting to use a user or basic auth e.g. "user:password@" is not - // allowed. Fragments ("#...") and query parameters ("?...") are not - // allowed, either. - // - // +optional - URL *string - - // `service` is a reference to the service for this webhook. Either - // `service` or `url` must be specified. - // - // If the webhook is running within the cluster, then you should use `service`. - // - // +optional - Service *ServiceReference - - // `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. - // If unspecified, system trust roots on the apiserver are used. - // +optional - CABundle []byte -} - -// ServiceReference holds a reference to Service.legacy.k8s.io -type ServiceReference struct { - // `namespace` is the namespace of the service. - // Required - Namespace string - // `name` is the name of the service. - // Required - Name string - - // `path` is an optional URL path which will be sent in any request to - // this service. - // +optional - Path *string - - // If specified, the port on the service that hosting webhook. - // `port` should be a valid port number (1-65535, inclusive). - // +optional - Port int32 -} - -// CustomResourceDefinitionVersion describes a version for CRD. -type CustomResourceDefinitionVersion struct { - // Name is the version name, e.g. “v1”, “v2beta1”, etc. - Name string - // Served is a flag enabling/disabling this version from being served via REST APIs - Served bool - // Storage flags the version as storage version. There must be exactly one flagged - // as storage version. - Storage bool - // deprecated indicates this version of the custom resource API is deprecated. - // When set to true, API requests to this version receive a warning header in the server response. - // Defaults to false. - Deprecated bool - // deprecationWarning overrides the default warning returned to API clients. - // May only be set when `deprecated` is true. - // The default warning indicates this version is deprecated and recommends use - // of the newest served version of equal or greater stability, if one exists. - DeprecationWarning *string - // Schema describes the schema for CustomResource used in validation, pruning, and defaulting. - // Top-level and per-version schemas are mutually exclusive. - // Per-version schemas must not all be set to identical values (top-level validation schema should be used instead) - // This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. - // +optional - Schema *CustomResourceValidation - // Subresources describes the subresources for CustomResource - // Top-level and per-version subresources are mutually exclusive. - // Per-version subresources must not all be set to identical values (top-level subresources should be used instead) - // This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. - // +optional - Subresources *CustomResourceSubresources - // AdditionalPrinterColumns are additional columns shown e.g. in kubectl next to the name. Defaults to a created-at column. - // Top-level and per-version columns are mutually exclusive. - // Per-version columns must not all be set to identical values (top-level columns should be used instead) - // This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature. - // NOTE: CRDs created prior to 1.13 populated the top-level additionalPrinterColumns field by default. To apply an - // update that changes to per-version additionalPrinterColumns, the top-level additionalPrinterColumns field must - // be explicitly set to null - // +optional - AdditionalPrinterColumns []CustomResourceColumnDefinition -} - -// CustomResourceColumnDefinition specifies a column for server side printing. -type CustomResourceColumnDefinition struct { - // name is a human readable name for the column. - Name string - // type is an OpenAPI type definition for this column. - // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. - Type string - // format is an optional OpenAPI type definition for this column. The 'name' format is applied - // to the primary identifier column to assist in clients identifying column is the resource name. - // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. - Format string - // description is a human readable description of this column. - Description string - // priority is an integer defining the relative importance of this column compared to others. Lower - // numbers are considered higher priority. Columns that may be omitted in limited space scenarios - // should be given a higher priority. - Priority int32 - - // JSONPath is a simple JSON path, i.e. without array notation. - JSONPath string -} - -// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition -type CustomResourceDefinitionNames struct { - // Plural is the plural name of the resource to serve. It must match the name of the CustomResourceDefinition-registration - // too: plural.group and it must be all lowercase. - Plural string - // Singular is the singular name of the resource. It must be all lowercase Defaults to lowercased - Singular string - // ShortNames are short names for the resource. It must be all lowercase. - ShortNames []string - // Kind is the serialized kind of the resource. It is normally CamelCase and singular. - Kind string - // ListKind is the serialized kind of the list for this resource. Defaults to List. - ListKind string - // Categories is a list of grouped resources custom resources belong to (e.g. 'all') - // +optional - Categories []string -} - -// ResourceScope is an enum defining the different scopes available to a custom resource -type ResourceScope string - -const ( - ClusterScoped ResourceScope = "Cluster" - NamespaceScoped ResourceScope = "Namespaced" -) - -type ConditionStatus string - -// These are valid condition statuses. "ConditionTrue" means a resource is in the condition. -// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes -// can't decide if a resource is in the condition or not. In the future, we could add other -// intermediate conditions, e.g. ConditionDegraded. -const ( - ConditionTrue ConditionStatus = "True" - ConditionFalse ConditionStatus = "False" - ConditionUnknown ConditionStatus = "Unknown" -) - -// CustomResourceDefinitionConditionType is a valid value for CustomResourceDefinitionCondition.Type -type CustomResourceDefinitionConditionType string - -const ( - // Established means that the resource has become active. A resource is established when all names are - // accepted without a conflict for the first time. A resource stays established until deleted, even during - // a later NamesAccepted due to changed names. Note that not all names can be changed. - Established CustomResourceDefinitionConditionType = "Established" - // NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in - // the group and are therefore accepted. - NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted" - // NonStructuralSchema means that one or more OpenAPI schema is not structural. - // - // A schema is structural if it specifies types for all values, with the only exceptions of those with - // - x-kubernetes-int-or-string: true — for fields which can be integer or string - // - x-kubernetes-preserve-unknown-fields: true — for raw, unspecified JSON values - // and there is no type, additionalProperties, default, nullable or x-kubernetes-* vendor extenions - // specified under allOf, anyOf, oneOf or not. - // - // Non-structural schemas will not be allowed anymore in v1 API groups. Moreover, new features will not be - // available for non-structural CRDs: - // - pruning - // - defaulting - // - read-only - // - OpenAPI publishing - // - webhook conversion - NonStructuralSchema CustomResourceDefinitionConditionType = "NonStructuralSchema" - // Terminating means that the CustomResourceDefinition has been deleted and is cleaning up. - Terminating CustomResourceDefinitionConditionType = "Terminating" - // KubernetesAPIApprovalPolicyConformant indicates that an API in *.k8s.io or *.kubernetes.io is or is not approved. For CRDs - // outside those groups, this condition will not be set. For CRDs inside those groups, the condition will - // be true if .metadata.annotations["api-approved.kubernetes.io"] is set to a URL, otherwise it will be false. - // See https://github.com/kubernetes/enhancements/pull/1111 for more details. - KubernetesAPIApprovalPolicyConformant CustomResourceDefinitionConditionType = "KubernetesAPIApprovalPolicyConformant" -) - -// CustomResourceDefinitionCondition contains details for the current condition of this pod. -type CustomResourceDefinitionCondition struct { - // Type is the type of the condition. Types include Established, NamesAccepted and Terminating. - Type CustomResourceDefinitionConditionType - // Status is the status of the condition. - // Can be True, False, Unknown. - Status ConditionStatus - // Last time the condition transitioned from one status to another. - // +optional - LastTransitionTime metav1.Time - // Unique, one-word, CamelCase reason for the condition's last transition. - // +optional - Reason string - // Human-readable message indicating details about last transition. - // +optional - Message string -} - -// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition -type CustomResourceDefinitionStatus struct { - // Conditions indicate state for particular aspects of a CustomResourceDefinition - // +listType=map - // +listMapKey=type - Conditions []CustomResourceDefinitionCondition - - // AcceptedNames are the names that are actually being used to serve discovery - // They may be different than the names in spec. - AcceptedNames CustomResourceDefinitionNames - - // StoredVersions are all versions of CustomResources that were ever persisted. Tracking these - // versions allows a migration path for stored versions in etcd. The field is mutable - // so the migration controller can first finish a migration to another version (i.e. - // that no old objects are left in the storage), and then remove the rest of the - // versions from this list. - // None of the versions in this list can be removed from the spec.Versions field. - StoredVersions []string -} - -// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of -// a CustomResourceDefinition -const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io" - -// +genclient -// +genclient:nonNamespaced -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format -// <.spec.name>.<.spec.group>. -type CustomResourceDefinition struct { - metav1.TypeMeta - metav1.ObjectMeta - - // Spec describes how the user wants the resources to appear - Spec CustomResourceDefinitionSpec - // Status indicates the actual state of the CustomResourceDefinition - Status CustomResourceDefinitionStatus -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// CustomResourceDefinitionList is a list of CustomResourceDefinition objects. -type CustomResourceDefinitionList struct { - metav1.TypeMeta - metav1.ListMeta - - // Items individual CustomResourceDefinitions - Items []CustomResourceDefinition -} - -// CustomResourceValidation is a list of validation methods for CustomResources. -type CustomResourceValidation struct { - // OpenAPIV3Schema is the OpenAPI v3 schema to be validated against. - OpenAPIV3Schema *JSONSchemaProps -} - -// CustomResourceSubresources defines the status and scale subresources for CustomResources. -type CustomResourceSubresources struct { - // Status denotes the status subresource for CustomResources - Status *CustomResourceSubresourceStatus - // Scale denotes the scale subresource for CustomResources - Scale *CustomResourceSubresourceScale -} - -// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources. -// Status is represented by the `.status` JSON path inside of a CustomResource. When set, -// * exposes a /status subresource for the custom resource -// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza -// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza -type CustomResourceSubresourceStatus struct{} - -// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources. -type CustomResourceSubresourceScale struct { - // SpecReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Spec.Replicas. - // Only JSON paths without the array notation are allowed. - // Must be a JSON Path under .spec. - // If there is no value under the given path in the CustomResource, the /scale subresource will return an error on GET. - SpecReplicasPath string - // StatusReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Replicas. - // Only JSON paths without the array notation are allowed. - // Must be a JSON Path under .status. - // If there is no value under the given path in the CustomResource, the status replica value in the /scale subresource - // will default to 0. - StatusReplicasPath string - // LabelSelectorPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Selector. - // Only JSON paths without the array notation are allowed. - // Must be a JSON Path under .status or .spec. - // Must be set to work with HPA. - // The field pointed by this JSON path must be a string field (not a complex selector struct) - // which contains a serialized label selector in string form. - // More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource - // If there is no value under the given path in the CustomResource, the status label selector value in the /scale - // subresource will default to the empty string. - // +optional - LabelSelectorPath *string -} diff --git a/encoding/crd/k8s/apiextensions/v1/types.go b/encoding/crd/k8s/apiextensions/v1/types.go new file mode 100644 index 00000000000..409e69eef52 --- /dev/null +++ b/encoding/crd/k8s/apiextensions/v1/types.go @@ -0,0 +1,439 @@ +/* +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 v1 + +import ( + "cuelang.org/go/encoding/crd/k8s/metav1" +) + +// ConversionStrategyType describes different conversion types. +type ConversionStrategyType string + +const ( + // KubeAPIApprovedAnnotation is an annotation that must be set to create a CRD for the k8s.io, *.k8s.io, kubernetes.io, or *.kubernetes.io namespaces. + // The value should be a link to a URL where the current spec was approved, so updates to the spec should also update the URL. + // If the API is unapproved, you may set the annotation to a string starting with `"unapproved"`. For instance, `"unapproved, temporarily squatting"` or `"unapproved, experimental-only"`. This is discouraged. + KubeAPIApprovedAnnotation = "api-approved.kubernetes.io" + + // NoneConverter is a converter that only sets apiversion of the CR and leave everything else unchanged. + NoneConverter ConversionStrategyType = "None" + // WebhookConverter is a converter that calls to an external webhook to convert the CR. + WebhookConverter ConversionStrategyType = "Webhook" +) + +// CustomResourceDefinitionSpec describes how a user wants their resource to appear +type CustomResourceDefinitionSpec struct { + // group is the API group of the defined custom resource. + // The custom resources are served under `/apis//...`. + // Must match the name of the CustomResourceDefinition (in the form `.`). + Group string `json:"group" protobuf:"bytes,1,opt,name=group"` + // names specify the resource and kind names for the custom resource. + Names CustomResourceDefinitionNames `json:"names" protobuf:"bytes,3,opt,name=names"` + // scope indicates whether the defined custom resource is cluster- or namespace-scoped. + // Allowed values are `Cluster` and `Namespaced`. + Scope ResourceScope `json:"scope" protobuf:"bytes,4,opt,name=scope,casttype=ResourceScope"` + // versions is the list of all API versions of the defined custom resource. + // Version names are used to compute the order in which served versions are listed in API discovery. + // If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered + // lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version), + // then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first + // by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing + // major version, then minor version. An example sorted list of versions: + // v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10. + Versions []CustomResourceDefinitionVersion `json:"versions" protobuf:"bytes,7,rep,name=versions"` + + // conversion defines conversion settings for the CRD. + // +optional + Conversion *CustomResourceConversion `json:"conversion,omitempty" protobuf:"bytes,9,opt,name=conversion"` + + // preserveUnknownFields indicates that object fields which are not specified + // in the OpenAPI schema should be preserved when persisting to storage. + // apiVersion, kind, metadata and known fields inside metadata are always preserved. + // This field is deprecated in favor of setting `x-preserve-unknown-fields` to true in `spec.versions[*].schema.openAPIV3Schema`. + // See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning for details. + // +optional + PreserveUnknownFields bool `json:"preserveUnknownFields,omitempty" protobuf:"varint,10,opt,name=preserveUnknownFields"` +} + +// CustomResourceConversion describes how to convert different versions of a CR. +type CustomResourceConversion struct { + // strategy specifies how custom resources are converted between versions. Allowed values are: + // - `"None"`: The converter only change the apiVersion and would not touch any other field in the custom resource. + // - `"Webhook"`: API Server will call to an external webhook to do the conversion. Additional information + // is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set. + Strategy ConversionStrategyType `json:"strategy" protobuf:"bytes,1,name=strategy"` + + // webhook describes how to call the conversion webhook. Required when `strategy` is set to `"Webhook"`. + // +optional + Webhook *WebhookConversion `json:"webhook,omitempty" protobuf:"bytes,2,opt,name=webhook"` +} + +// WebhookConversion describes how to call a conversion webhook +type WebhookConversion struct { + // clientConfig is the instructions for how to call the webhook if strategy is `Webhook`. + // +optional + ClientConfig *WebhookClientConfig `json:"clientConfig,omitempty" protobuf:"bytes,2,name=clientConfig"` + + // conversionReviewVersions is an ordered list of preferred `ConversionReview` + // versions the Webhook expects. The API server will use the first version in + // the list which it supports. If none of the versions specified in this list + // are supported by API server, conversion will fail for the custom resource. + // If a persisted Webhook configuration specifies allowed versions and does not + // include any versions known to the API Server, calls to the webhook will fail. + ConversionReviewVersions []string `json:"conversionReviewVersions" protobuf:"bytes,3,rep,name=conversionReviewVersions"` +} + +// WebhookClientConfig contains the information to make a TLS connection with the webhook. +type WebhookClientConfig struct { + // url gives the location of the webhook, in standard URL form + // (`scheme://host:port/path`). Exactly one of `url` or `service` + // must be specified. + // + // The `host` should not refer to a service running in the cluster; use + // the `service` field instead. The host might be resolved via external + // DNS in some apiservers (e.g., `kube-apiserver` cannot resolve + // in-cluster DNS as that would be a layering violation). `host` may + // also be an IP address. + // + // Please note that using `localhost` or `127.0.0.1` as a `host` is + // risky unless you take great care to run this webhook on all hosts + // which run an apiserver which might need to make calls to this + // webhook. Such installs are likely to be non-portable, i.e., not easy + // to turn up in a new cluster. + // + // The scheme must be "https"; the URL must begin with "https://". + // + // A path is optional, and if present may be any string permissible in + // a URL. You may use the path to pass an arbitrary string to the + // webhook, for example, a cluster identifier. + // + // Attempting to use a user or basic auth e.g. "user:password@" is not + // allowed. Fragments ("#...") and query parameters ("?...") are not + // allowed, either. + // + // +optional + URL *string `json:"url,omitempty" protobuf:"bytes,3,opt,name=url"` + + // service is a reference to the service for this webhook. Either + // service or url must be specified. + // + // If the webhook is running within the cluster, then you should use `service`. + // + // +optional + Service *ServiceReference `json:"service,omitempty" protobuf:"bytes,1,opt,name=service"` + + // caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. + // If unspecified, system trust roots on the apiserver are used. + // +optional + CABundle []byte `json:"caBundle,omitempty" protobuf:"bytes,2,opt,name=caBundle"` +} + +// ServiceReference holds a reference to Service.legacy.k8s.io +type ServiceReference struct { + // namespace is the namespace of the service. + // Required + Namespace string `json:"namespace" protobuf:"bytes,1,opt,name=namespace"` + // name is the name of the service. + // Required + Name string `json:"name" protobuf:"bytes,2,opt,name=name"` + + // path is an optional URL path at which the webhook will be contacted. + // +optional + Path *string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"` + + // port is an optional service port at which the webhook will be contacted. + // `port` should be a valid port number (1-65535, inclusive). + // Defaults to 443 for backward compatibility. + // +optional + Port *int32 `json:"port,omitempty" protobuf:"varint,4,opt,name=port"` +} + +// CustomResourceDefinitionVersion describes a version for CRD. +type CustomResourceDefinitionVersion struct { + // name is the version name, e.g. “v1”, “v2beta1”, etc. + // The custom resources are served under this version at `/apis///...` if `served` is true. + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + // served is a flag enabling/disabling this version from being served via REST APIs + Served bool `json:"served" protobuf:"varint,2,opt,name=served"` + // storage indicates this version should be used when persisting custom resources to storage. + // There must be exactly one version with storage=true. + Storage bool `json:"storage" protobuf:"varint,3,opt,name=storage"` + // deprecated indicates this version of the custom resource API is deprecated. + // When set to true, API requests to this version receive a warning header in the server response. + // Defaults to false. + // +optional + Deprecated bool `json:"deprecated,omitempty" protobuf:"varint,7,opt,name=deprecated"` + // deprecationWarning overrides the default warning returned to API clients. + // May only be set when `deprecated` is true. + // The default warning indicates this version is deprecated and recommends use + // of the newest served version of equal or greater stability, if one exists. + // +optional + DeprecationWarning *string `json:"deprecationWarning,omitempty" protobuf:"bytes,8,opt,name=deprecationWarning"` + // schema describes the schema used for validation, pruning, and defaulting of this version of the custom resource. + // +optional + Schema *CustomResourceValidation `json:"schema,omitempty" protobuf:"bytes,4,opt,name=schema"` + // subresources specify what subresources this version of the defined custom resource have. + // +optional + Subresources *CustomResourceSubresources `json:"subresources,omitempty" protobuf:"bytes,5,opt,name=subresources"` + // additionalPrinterColumns specifies additional columns returned in Table output. + // See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details. + // If no columns are specified, a single column displaying the age of the custom resource is used. + // +optional + AdditionalPrinterColumns []CustomResourceColumnDefinition `json:"additionalPrinterColumns,omitempty" protobuf:"bytes,6,rep,name=additionalPrinterColumns"` +} + +// CustomResourceColumnDefinition specifies a column for server side printing. +type CustomResourceColumnDefinition struct { + // name is a human readable name for the column. + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + // type is an OpenAPI type definition for this column. + // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details. + Type string `json:"type" protobuf:"bytes,2,opt,name=type"` + // format is an optional OpenAPI type definition for this column. The 'name' format is applied + // to the primary identifier column to assist in clients identifying column is the resource name. + // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details. + // +optional + Format string `json:"format,omitempty" protobuf:"bytes,3,opt,name=format"` + // description is a human readable description of this column. + // +optional + Description string `json:"description,omitempty" protobuf:"bytes,4,opt,name=description"` + // priority is an integer defining the relative importance of this column compared to others. Lower + // numbers are considered higher priority. Columns that may be omitted in limited space scenarios + // should be given a priority greater than 0. + // +optional + Priority int32 `json:"priority,omitempty" protobuf:"bytes,5,opt,name=priority"` + // jsonPath is a simple JSON path (i.e. with array notation) which is evaluated against + // each custom resource to produce the value for this column. + JSONPath string `json:"jsonPath" protobuf:"bytes,6,opt,name=jsonPath"` +} + +// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition +type CustomResourceDefinitionNames struct { + // plural is the plural name of the resource to serve. + // The custom resources are served under `/apis///.../`. + // Must match the name of the CustomResourceDefinition (in the form `.`). + // Must be all lowercase. + Plural string `json:"plural" protobuf:"bytes,1,opt,name=plural"` + // singular is the singular name of the resource. It must be all lowercase. Defaults to lowercased `kind`. + // +optional + Singular string `json:"singular,omitempty" protobuf:"bytes,2,opt,name=singular"` + // shortNames are short names for the resource, exposed in API discovery documents, + // and used by clients to support invocations like `kubectl get `. + // It must be all lowercase. + // +optional + ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,3,opt,name=shortNames"` + // kind is the serialized kind of the resource. It is normally CamelCase and singular. + // Custom resource instances will use this value as the `kind` attribute in API calls. + Kind string `json:"kind" protobuf:"bytes,4,opt,name=kind"` + // listKind is the serialized kind of the list for this resource. Defaults to "`kind`List". + // +optional + ListKind string `json:"listKind,omitempty" protobuf:"bytes,5,opt,name=listKind"` + // categories is a list of grouped resources this custom resource belongs to (e.g. 'all'). + // This is published in API discovery documents, and used by clients to support invocations like + // `kubectl get all`. + // +optional + Categories []string `json:"categories,omitempty" protobuf:"bytes,6,rep,name=categories"` +} + +// ResourceScope is an enum defining the different scopes available to a custom resource +type ResourceScope string + +const ( + ClusterScoped ResourceScope = "Cluster" + NamespaceScoped ResourceScope = "Namespaced" +) + +type ConditionStatus string + +// These are valid condition statuses. "ConditionTrue" means a resource is in the condition. +// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes +// can't decide if a resource is in the condition or not. In the future, we could add other +// intermediate conditions, e.g. ConditionDegraded. +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" + ConditionUnknown ConditionStatus = "Unknown" +) + +// CustomResourceDefinitionConditionType is a valid value for CustomResourceDefinitionCondition.Type +type CustomResourceDefinitionConditionType string + +const ( + // Established means that the resource has become active. A resource is established when all names are + // accepted without a conflict for the first time. A resource stays established until deleted, even during + // a later NamesAccepted due to changed names. Note that not all names can be changed. + Established CustomResourceDefinitionConditionType = "Established" + // NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in + // the group and are therefore accepted. + NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted" + // NonStructuralSchema means that one or more OpenAPI schema is not structural. + // + // A schema is structural if it specifies types for all values, with the only exceptions of those with + // - x-kubernetes-int-or-string: true — for fields which can be integer or string + // - x-kubernetes-preserve-unknown-fields: true — for raw, unspecified JSON values + // and there is no type, additionalProperties, default, nullable or x-kubernetes-* vendor extenions + // specified under allOf, anyOf, oneOf or not. + // + // Non-structural schemas will not be allowed anymore in v1 API groups. Moreover, new features will not be + // available for non-structural CRDs: + // - pruning + // - defaulting + // - read-only + // - OpenAPI publishing + // - webhook conversion + NonStructuralSchema CustomResourceDefinitionConditionType = "NonStructuralSchema" + // Terminating means that the CustomResourceDefinition has been deleted and is cleaning up. + Terminating CustomResourceDefinitionConditionType = "Terminating" + // KubernetesAPIApprovalPolicyConformant indicates that an API in *.k8s.io or *.kubernetes.io is or is not approved. For CRDs + // outside those groups, this condition will not be set. For CRDs inside those groups, the condition will + // be true if .metadata.annotations["api-approved.kubernetes.io"] is set to a URL, otherwise it will be false. + // See https://github.com/kubernetes/enhancements/pull/1111 for more details. + KubernetesAPIApprovalPolicyConformant CustomResourceDefinitionConditionType = "KubernetesAPIApprovalPolicyConformant" +) + +// CustomResourceDefinitionCondition contains details for the current condition of this pod. +type CustomResourceDefinitionCondition struct { + // type is the type of the condition. Types include Established, NamesAccepted and Terminating. + Type CustomResourceDefinitionConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=CustomResourceDefinitionConditionType"` + // status is the status of the condition. + // Can be True, False, Unknown. + Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"` + // lastTransitionTime last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"` + // reason is a unique, one-word, CamelCase reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` + // message is a human-readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"` +} + +// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition +type CustomResourceDefinitionStatus struct { + // conditions indicate state for particular aspects of a CustomResourceDefinition + // +optional + // +listType=map + // +listMapKey=type + Conditions []CustomResourceDefinitionCondition `json:"conditions" protobuf:"bytes,1,opt,name=conditions"` + + // acceptedNames are the names that are actually being used to serve discovery. + // They may be different than the names in spec. + // +optional + AcceptedNames CustomResourceDefinitionNames `json:"acceptedNames" protobuf:"bytes,2,opt,name=acceptedNames"` + + // storedVersions lists all versions of CustomResources that were ever persisted. Tracking these + // versions allows a migration path for stored versions in etcd. The field is mutable + // so a migration controller can finish a migration to another version (ensuring + // no old objects are left in storage), and then remove the rest of the + // versions from this list. + // Versions may not be removed from `spec.versions` while they exist in this list. + // +optional + StoredVersions []string `json:"storedVersions" protobuf:"bytes,3,rep,name=storedVersions"` +} + +// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of +// a CustomResourceDefinition +const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io" + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format +// <.spec.name>.<.spec.group>. +type CustomResourceDefinition struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // spec describes how the user wants the resources to appear + Spec CustomResourceDefinitionSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` + // status indicates the actual state of the CustomResourceDefinition + // +optional + Status CustomResourceDefinitionStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// CustomResourceDefinitionList is a list of CustomResourceDefinition objects. +type CustomResourceDefinitionList struct { + metav1.TypeMeta `json:",inline"` + + // Standard object's metadata + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // items list individual CustomResourceDefinition objects + Items []CustomResourceDefinition `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// CustomResourceValidation is a list of validation methods for CustomResources. +type CustomResourceValidation struct { + // openAPIV3Schema is the OpenAPI v3 schema to use for validation and pruning. + // +optional + OpenAPIV3Schema *JSONSchemaProps `json:"openAPIV3Schema,omitempty" protobuf:"bytes,1,opt,name=openAPIV3Schema"` +} + +// CustomResourceSubresources defines the status and scale subresources for CustomResources. +type CustomResourceSubresources struct { + // status indicates the custom resource should serve a `/status` subresource. + // When enabled: + // 1. requests to the custom resource primary endpoint ignore changes to the `status` stanza of the object. + // 2. requests to the custom resource `/status` subresource ignore changes to anything other than the `status` stanza of the object. + // +optional + Status *CustomResourceSubresourceStatus `json:"status,omitempty" protobuf:"bytes,1,opt,name=status"` + // scale indicates the custom resource should serve a `/scale` subresource that returns an `autoscaling/v1` Scale object. + // +optional + Scale *CustomResourceSubresourceScale `json:"scale,omitempty" protobuf:"bytes,2,opt,name=scale"` +} + +// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources. +// Status is represented by the `.status` JSON path inside of a CustomResource. When set, +// * exposes a /status subresource for the custom resource +// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza +// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza +type CustomResourceSubresourceStatus struct{} + +// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources. +type CustomResourceSubresourceScale struct { + // specReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `spec.replicas`. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under `.spec`. + // If there is no value under the given path in the custom resource, the `/scale` subresource will return an error on GET. + SpecReplicasPath string `json:"specReplicasPath" protobuf:"bytes,1,name=specReplicasPath"` + // statusReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `status.replicas`. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under `.status`. + // If there is no value under the given path in the custom resource, the `status.replicas` value in the `/scale` subresource + // will default to 0. + StatusReplicasPath string `json:"statusReplicasPath" protobuf:"bytes,2,opt,name=statusReplicasPath"` + // labelSelectorPath defines the JSON path inside of a custom resource that corresponds to Scale `status.selector`. + // Only JSON paths without the array notation are allowed. + // Must be a JSON Path under `.status` or `.spec`. + // Must be set to work with HorizontalPodAutoscaler. + // The field pointed by this JSON path must be a string field (not a complex selector struct) + // which contains a serialized label selector in string form. + // More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource + // If there is no value under the given path in the custom resource, the `status.selector` value in the `/scale` + // subresource will default to the empty string. + // +optional + LabelSelectorPath *string `json:"labelSelectorPath,omitempty" protobuf:"bytes,3,opt,name=labelSelectorPath"` +} diff --git a/encoding/crd/k8s/apiextensions/types_jsonschema.go b/encoding/crd/k8s/apiextensions/v1/types_jsonschema.go similarity index 53% rename from encoding/crd/k8s/apiextensions/types_jsonschema.go rename to encoding/crd/k8s/apiextensions/v1/types_jsonschema.go index cc1c7437fcd..cff1d27924a 100644 --- a/encoding/crd/k8s/apiextensions/types_jsonschema.go +++ b/encoding/crd/k8s/apiextensions/v1/types_jsonschema.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +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. @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package apiextensions +package v1 // FieldValueErrorReason is a machine-readable value providing more detail about why a field failed the validation. // +enum @@ -38,43 +38,74 @@ const ( // JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/). type JSONSchemaProps struct { - ID string - Schema JSONSchemaURL - Ref *string - Description string - Type string - Nullable bool - Format string - Title string - Default *JSON - Maximum *float64 - ExclusiveMaximum bool - Minimum *float64 - ExclusiveMinimum bool - MaxLength *int64 - MinLength *int64 - Pattern string - MaxItems *int64 - MinItems *int64 - UniqueItems bool - MultipleOf *float64 - Enum []JSON - MaxProperties *int64 - MinProperties *int64 - Required []string - Items *JSONSchemaPropsOrArray - AllOf []JSONSchemaProps - OneOf []JSONSchemaProps - AnyOf []JSONSchemaProps - Not *JSONSchemaProps - Properties map[string]JSONSchemaProps - AdditionalProperties *JSONSchemaPropsOrBool - PatternProperties map[string]JSONSchemaProps - Dependencies JSONSchemaDependencies - AdditionalItems *JSONSchemaPropsOrBool - Definitions JSONSchemaDefinitions - ExternalDocs *ExternalDocumentation - Example *JSON + ID string `json:"id,omitempty" protobuf:"bytes,1,opt,name=id"` + Schema JSONSchemaURL `json:"$schema,omitempty" protobuf:"bytes,2,opt,name=schema"` + Ref *string `json:"$ref,omitempty" protobuf:"bytes,3,opt,name=ref"` + Description string `json:"description,omitempty" protobuf:"bytes,4,opt,name=description"` + Type string `json:"type,omitempty" protobuf:"bytes,5,opt,name=type"` + + // format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated: + // + // - bsonobjectid: a bson object ID, i.e. a 24 characters hex string + // - uri: an URI as parsed by Golang net/url.ParseRequestURI + // - email: an email address as parsed by Golang net/mail.ParseAddress + // - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034]. + // - ipv4: an IPv4 IP as parsed by Golang net.ParseIP + // - ipv6: an IPv6 IP as parsed by Golang net.ParseIP + // - cidr: a CIDR as parsed by Golang net.ParseCIDR + // - mac: a MAC address as parsed by Golang net.ParseMAC + // - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + // - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ + // - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + // - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ + // - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041" + // - isbn10: an ISBN10 number string like "0321751043" + // - isbn13: an ISBN13 number string like "978-0321751041" + // - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in + // - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$ + // - hexcolor: an hexadecimal color code like "#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ + // - rgbcolor: an RGB color code like rgb like "rgb(255,255,2559" + // - byte: base64 encoded binary data + // - password: any kind of string + // - date: a date string like "2006-01-02" as defined by full-date in RFC3339 + // - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format + // - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339. + Format string `json:"format,omitempty" protobuf:"bytes,6,opt,name=format"` + + Title string `json:"title,omitempty" protobuf:"bytes,7,opt,name=title"` + // default is a default value for undefined object fields. + // Defaulting is a beta feature under the CustomResourceDefaulting feature gate. + // Defaulting requires spec.preserveUnknownFields to be false. + Default *JSON `json:"default,omitempty" protobuf:"bytes,8,opt,name=default"` + Maximum *float64 `json:"maximum,omitempty" protobuf:"bytes,9,opt,name=maximum"` + ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty" protobuf:"bytes,10,opt,name=exclusiveMaximum"` + Minimum *float64 `json:"minimum,omitempty" protobuf:"bytes,11,opt,name=minimum"` + ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty" protobuf:"bytes,12,opt,name=exclusiveMinimum"` + MaxLength *int64 `json:"maxLength,omitempty" protobuf:"bytes,13,opt,name=maxLength"` + MinLength *int64 `json:"minLength,omitempty" protobuf:"bytes,14,opt,name=minLength"` + Pattern string `json:"pattern,omitempty" protobuf:"bytes,15,opt,name=pattern"` + MaxItems *int64 `json:"maxItems,omitempty" protobuf:"bytes,16,opt,name=maxItems"` + MinItems *int64 `json:"minItems,omitempty" protobuf:"bytes,17,opt,name=minItems"` + UniqueItems bool `json:"uniqueItems,omitempty" protobuf:"bytes,18,opt,name=uniqueItems"` + MultipleOf *float64 `json:"multipleOf,omitempty" protobuf:"bytes,19,opt,name=multipleOf"` + Enum []JSON `json:"enum,omitempty" protobuf:"bytes,20,rep,name=enum"` + MaxProperties *int64 `json:"maxProperties,omitempty" protobuf:"bytes,21,opt,name=maxProperties"` + MinProperties *int64 `json:"minProperties,omitempty" protobuf:"bytes,22,opt,name=minProperties"` + Required []string `json:"required,omitempty" protobuf:"bytes,23,rep,name=required"` + Items *JSONSchemaPropsOrArray `json:"items,omitempty" protobuf:"bytes,24,opt,name=items"` + AllOf []JSONSchemaProps `json:"allOf,omitempty" protobuf:"bytes,25,rep,name=allOf"` + OneOf []JSONSchemaProps `json:"oneOf,omitempty" protobuf:"bytes,26,rep,name=oneOf"` + AnyOf []JSONSchemaProps `json:"anyOf,omitempty" protobuf:"bytes,27,rep,name=anyOf"` + Not *JSONSchemaProps `json:"not,omitempty" protobuf:"bytes,28,opt,name=not"` + Properties map[string]JSONSchemaProps `json:"properties,omitempty" protobuf:"bytes,29,rep,name=properties"` + AdditionalProperties *JSONSchemaPropsOrBool `json:"additionalProperties,omitempty" protobuf:"bytes,30,opt,name=additionalProperties"` + PatternProperties map[string]JSONSchemaProps `json:"patternProperties,omitempty" protobuf:"bytes,31,rep,name=patternProperties"` + Dependencies JSONSchemaDependencies `json:"dependencies,omitempty" protobuf:"bytes,32,opt,name=dependencies"` + AdditionalItems *JSONSchemaPropsOrBool `json:"additionalItems,omitempty" protobuf:"bytes,33,opt,name=additionalItems"` + Definitions JSONSchemaDefinitions `json:"definitions,omitempty" protobuf:"bytes,34,opt,name=definitions"` + ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty" protobuf:"bytes,35,opt,name=externalDocs"` + Example *JSON `json:"example,omitempty" protobuf:"bytes,36,opt,name=example"` + Nullable bool `json:"nullable,omitempty" protobuf:"bytes,37,opt,name=nullable"` // x-kubernetes-preserve-unknown-fields stops the API server // decoding step from pruning fields which are not specified @@ -82,15 +113,16 @@ type JSONSchemaProps struct { // but switches back to normal pruning behaviour if nested // properties or additionalProperties are specified in the schema. // This can either be true or undefined. False is forbidden. - XPreserveUnknownFields *bool + XPreserveUnknownFields *bool `json:"x-kubernetes-preserve-unknown-fields,omitempty" protobuf:"bytes,38,opt,name=xKubernetesPreserveUnknownFields"` // x-kubernetes-embedded-resource defines that the value is an // embedded Kubernetes runtime.Object, with TypeMeta and // ObjectMeta. The type must be object. It is allowed to further - // restrict the embedded object. Both ObjectMeta and TypeMeta + // restrict the embedded object. kind, apiVersion and metadata // are validated automatically. x-kubernetes-preserve-unknown-fields - // must be true. - XEmbeddedResource bool + // is allowed to be true, but does not have to be if the object + // is fully specified (up to kind, apiVersion, metadata). + XEmbeddedResource bool `json:"x-kubernetes-embedded-resource,omitempty" protobuf:"bytes,39,opt,name=xKubernetesEmbeddedResource"` // x-kubernetes-int-or-string specifies that this value is // either an integer or a string. If this is true, an empty @@ -105,7 +137,7 @@ type JSONSchemaProps struct { // - type: integer // - type: string // - ... zero or more - XIntOrString bool + XIntOrString bool `json:"x-kubernetes-int-or-string,omitempty" protobuf:"bytes,40,opt,name=xKubernetesIntOrString"` // x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used // as the index of the map. @@ -113,7 +145,12 @@ type JSONSchemaProps struct { // This tag MUST only be used on lists that have the "x-kubernetes-list-type" // extension set to "map". Also, the values specified for this attribute must // be a scalar typed field of the child structure (no nesting is supported). - XListMapKeys []string + // + // The properties specified must either be required or have a default value, + // to ensure those properties are present for all list items. + // + // +optional + XListMapKeys []string `json:"x-kubernetes-list-map-keys,omitempty" protobuf:"bytes,41,rep,name=xKubernetesListMapKeys"` // x-kubernetes-list-type annotates an array to further describe its topology. // This extension must only be used on lists and may have 3 possible values: @@ -129,7 +166,9 @@ type JSONSchemaProps struct { // These lists are like maps in that their elements have a non-index key // used to identify them. Order is preserved upon merge. The map tag // must only be used on a list with elements of type object. - XListType *string + // Defaults to atomic for arrays. + // +optional + XListType *string `json:"x-kubernetes-list-type,omitempty" protobuf:"bytes,42,opt,name=xKubernetesListType"` // x-kubernetes-map-type annotates an object to further describe its topology. // This extension must only be used when type is object and may have 2 possible values: @@ -141,15 +180,15 @@ type JSONSchemaProps struct { // 2) `atomic`: the list is treated as a single entity, like a scalar. // Atomic maps will be entirely replaced when updated. // +optional - XMapType *string + XMapType *string `json:"x-kubernetes-map-type,omitempty" protobuf:"bytes,43,opt,name=xKubernetesMapType"` - // x-kubernetes-validations -kubernetes-validations describes a list of validation rules written in the CEL expression language. + // x-kubernetes-validations describes a list of validation rules written in the CEL expression language. // This field is an alpha-level. Using this field requires the feature gate `CustomResourceValidationExpressions` to be enabled. // +patchMergeKey=rule // +patchStrategy=merge // +listType=map // +listMapKey=rule - XValidations ValidationRules + XValidations ValidationRules `json:"x-kubernetes-validations,omitempty" patchStrategy:"merge" patchMergeKey:"rule" protobuf:"bytes,44,rep,name=xKubernetesValidations"` } // ValidationRules describes a list of validation rules written in the CEL expression language. @@ -210,12 +249,12 @@ type ValidationRule struct { // - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values // are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with // non-intersecting keys are appended, retaining their partial order. - Rule string + Rule string `json:"rule" protobuf:"bytes,1,opt,name=rule"` // Message represents the message displayed when validation fails. The message is required if the Rule contains // line breaks. The message must not contain line breaks. // If unset, the message is "failed rule: {Rule}". // e.g. "must be a URL with the host matching spec.host" - Message string + Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"` // MessageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. // Since messageExpression is used as a failure message, it must evaluate to a string. // If both message and messageExpression are present on a rule, then messageExpression will be used if validation @@ -227,14 +266,14 @@ type ValidationRule struct { // Example: // "x must be less than max ("+string(self.max)+")" // +optional - MessageExpression string + MessageExpression string `json:"messageExpression,omitempty" protobuf:"bytes,3,opt,name=messageExpression"` // reason provides a machine-readable validation failure reason that is returned to the caller when a request fails this validation rule. // The HTTP status code returned to the caller will match the reason of the reason of the first failed validation rule. // The currently supported reasons are: "FieldValueInvalid", "FieldValueForbidden", "FieldValueRequired", "FieldValueDuplicate". // If not set, default to use "FieldValueInvalid". // All future added reasons must be accepted by clients when reading this value and unknown reasons should be treated as FieldValueInvalid. // +optional - Reason *FieldValueErrorReason + Reason *FieldValueErrorReason `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"` // fieldPath represents the field path returned when the validation fails. // It must be a relative JSON path (i.e. with array notation) scoped to the location of this x-kubernetes-validations extension in the schema and refer to an existing field. // e.g. when validation checks if a specific attribute `foo` under a map `testMap`, the fieldPath could be set to `.testMap.foo` @@ -245,12 +284,27 @@ type ValidationRule struct { // For field name which contains special characters, use `['specialName']` to refer the field name. // e.g. for attribute `foo.34$` appears in a list `testList`, the fieldPath could be set to `.testList['foo.34$']` // +optional - FieldPath string + FieldPath string `json:"fieldPath,omitempty" protobuf:"bytes,5,opt,name=fieldPath"` } // JSON represents any valid JSON value. // These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil. -type JSON interface{} +type JSON struct { + Raw []byte `json:"-" protobuf:"bytes,1,opt,name=raw"` +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ JSON) OpenAPISchemaType() []string { + // TODO: return actual types when anyOf is supported + return nil +} + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ JSON) OpenAPISchemaFormat() string { return "" } // JSONSchemaURL represents a schema url. type JSONSchemaURL string @@ -258,31 +312,70 @@ type JSONSchemaURL string // JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps // or an array of JSONSchemaProps. Mainly here for serialization purposes. type JSONSchemaPropsOrArray struct { - Schema *JSONSchemaProps - JSONSchemas []JSONSchemaProps + Schema *JSONSchemaProps `json:",inline" protobuf:"bytes,1,opt,name=schema"` + JSONSchemas []JSONSchemaProps `json:",inline" protobuf:"bytes,2,rep,name=jSONSchemas"` +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ JSONSchemaPropsOrArray) OpenAPISchemaType() []string { + // TODO: return actual types when anyOf is supported + return nil } +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ JSONSchemaPropsOrArray) OpenAPISchemaFormat() string { return "" } + // JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value. // Defaults to true for the boolean property. type JSONSchemaPropsOrBool struct { - Allows bool - Schema *JSONSchemaProps + Allows bool `json:",inline" protobuf:"varint,1,opt,name=allows"` + Schema *JSONSchemaProps `json:",inline" protobuf:"bytes,2,opt,name=schema"` +} + +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ JSONSchemaPropsOrBool) OpenAPISchemaType() []string { + // TODO: return actual types when anyOf is supported + return nil } +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ JSONSchemaPropsOrBool) OpenAPISchemaFormat() string { return "" } + // JSONSchemaDependencies represent a dependencies property. type JSONSchemaDependencies map[string]JSONSchemaPropsOrStringArray // JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array. type JSONSchemaPropsOrStringArray struct { - Schema *JSONSchemaProps - Property []string + Schema *JSONSchemaProps `json:",inline" protobuf:"bytes,1,opt,name=schema"` + Property []string `json:",inline" protobuf:"bytes,2,rep,name=property"` } +// OpenAPISchemaType is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +// +// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators +func (_ JSONSchemaPropsOrStringArray) OpenAPISchemaType() []string { + // TODO: return actual types when anyOf is supported + return nil +} + +// OpenAPISchemaFormat is used by the kube-openapi generator when constructing +// the OpenAPI spec of this type. +func (_ JSONSchemaPropsOrStringArray) OpenAPISchemaFormat() string { return "" } + // JSONSchemaDefinitions contains the models explicitly defined in this spec. type JSONSchemaDefinitions map[string]JSONSchemaProps // ExternalDocumentation allows referencing an external resource for extended documentation. type ExternalDocumentation struct { - Description string - URL string + Description string `json:"description,omitempty" protobuf:"bytes,1,opt,name=description"` + URL string `json:"url,omitempty" protobuf:"bytes,2,opt,name=url"` } diff --git a/encoding/crd/k8s/metav1/time.go b/encoding/crd/k8s/metav1/time.go index 4125bac4af7..d380462723c 100644 --- a/encoding/crd/k8s/metav1/time.go +++ b/encoding/crd/k8s/metav1/time.go @@ -29,7 +29,7 @@ import ( // +protobuf.as=Timestamp // +protobuf.options.(gogoproto.goproto_stringer)=false type Time struct { - time.Time `protobuf:"-"` + time.Time `json:",inline" protobuf:"-"` } // DeepCopyInto creates a deep-copy of the Time value. The underlying time.Time diff --git a/encoding/crd/parse.go b/encoding/crd/parse.go index 4af1098ad5c..80592fbdd56 100644 --- a/encoding/crd/parse.go +++ b/encoding/crd/parse.go @@ -19,7 +19,7 @@ import ( "fmt" "cuelang.org/go/cue" - "cuelang.org/go/encoding/crd/k8s/apiextensions" + apiextensions "cuelang.org/go/encoding/crd/k8s/apiextensions/v1" "cuelang.org/go/encoding/yaml" goyaml "gopkg.in/yaml.v3" ) From 9a190fbccffcf0f42c192ba0f993998517a7c725 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Wed, 22 Nov 2023 15:04:36 -0500 Subject: [PATCH 18/20] store og Signed-off-by: Justen Stall --- encoding/crd/convert.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/encoding/crd/convert.go b/encoding/crd/convert.go index 3bcb56e40e7..028228d0612 100644 --- a/encoding/crd/convert.go +++ b/encoding/crd/convert.go @@ -65,6 +65,10 @@ func (b *Extractor) Instances(crdData []byte) (map[string][]byte, error) { } } + for name := range result { + fmt.Println(name) + } + return result, nil } @@ -118,7 +122,8 @@ type VersionedSchema struct { func convertCRD(crd cue.Value) (*IntermediateCRD, error) { cc := &IntermediateCRD{ - Schemas: make([]VersionedSchema, 0), + Original: crd, + Schemas: make([]VersionedSchema, 0), } var err error From 09d70914191a2850f6d398b57d26209629a3599b Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Wed, 22 Nov 2023 16:20:58 -0500 Subject: [PATCH 19/20] get yaml unmarshalling working Signed-off-by: Justen Stall --- encoding/crd/convert.go | 39 ++++++++----------- .../k8s/apiextensions/v1/types_jsonschema.go | 20 +++++----- encoding/crd/k8s/metav1/time.go | 2 +- encoding/crd/parse.go | 4 +- go.mod | 1 + go.sum | 2 + 6 files changed, 33 insertions(+), 35 deletions(-) diff --git a/encoding/crd/convert.go b/encoding/crd/convert.go index 028228d0612..95fb0dd67cc 100644 --- a/encoding/crd/convert.go +++ b/encoding/crd/convert.go @@ -65,9 +65,9 @@ func (b *Extractor) Instances(crdData []byte) (map[string][]byte, error) { } } - for name := range result { - fmt.Println(name) - } + // for name := range result { + // fmt.Println(name) + // } return result, nil } @@ -198,7 +198,7 @@ func convertCRD(crd cue.Value) (*IntermediateCRD, error) { // Add attributes for k8s oapi extensions // construct a map of all paths using x-kubernetes-* OpenAPI extensions - sch = mapAttributes(sch, rootosch) + sch = mapAttributes(sch.LookupPath(defpath), rootosch) // now, go back to an AST because it's easier to manipulate references there var schast *ast.File @@ -394,10 +394,9 @@ const ( // Preserves Kubernetes OpenAPI extensions in an attribute for each field utilizing them func mapAttributes(val cue.Value, prop apiextensions.JSONSchemaProps) cue.Value { - attr := xk8sattr(*val.Context(), prop) + attr := extensionAttribute(val.Context(), prop) + // val = val.FillPath(val.Path(), ast.NewLit(token.ATTRIBUTE, attr.Text)) if attr != nil { - _, p := val.ReferencePath() - fmt.Println(p.String() + ": " + attr.Text) node := val.Source() switch x := node.(type) { case *ast.StructLit: @@ -417,8 +416,12 @@ func mapAttributes(val cue.Value, prop apiextensions.JSONSchemaProps) cue.Value for name := range prop.Properties { // Recursively add subextensions for each property nextPath := cue.MakePath(cue.Str(name)) - nextVal := mapAttributes(val.LookupPath(nextPath), prop.Properties[name]) - val = val.FillPath(nextPath, nextVal) + nextVal := val.LookupPath(nextPath) + if nextVal.Kind() == cue.BottomKind { + continue + } + val = val.FillPath(nextPath, mapAttributes(nextVal, prop.Properties[name])) + // val = val.Unify(nextVal) } // TODO: array does not work right, see https://github.com/istio/istio/blob/0d5f530188dfe571bf0d8f515618ba99a0dc3e6c/manifests/charts/base/crds/crd-all.gen.yaml#L188 @@ -438,21 +441,11 @@ func mapAttributes(val cue.Value, prop apiextensions.JSONSchemaProps) cue.Value val = val.FillPath(nextVal.Path(), mapAttributes(nextVal, prop.Items.JSONSchemas[i])) } } else { - // if val.Allows(cue.AnyIndex) { anyIndex := cue.MakePath(cue.AnyIndex) - val.LookupPath(cue.MakePath(cue.AnyIndex)) nextVal := val.LookupPath(anyIndex) - fmt.Println(nextVal) - nextVal = mapAttributes(nextVal, *prop.Items.Schema) - val = val.FillPath(anyIndex, nextVal) - // } else { - // fmt.Println("here") - // } - - // Add attribute to the pattern constraint - // // Recursively add subextensions for each property - // subExts := xKubernetesAttributes(append(path, cue.AnyIndex), *prop.Items.Schema) - // extensions = append(extensions, subExts...) + if nextVal.Kind() != cue.BottomKind { + val = val.FillPath(anyIndex, mapAttributes(nextVal, *prop.Items.Schema)) + } } } @@ -485,7 +478,7 @@ func (kv keyval) String() string { return kv.key } -func xk8sattr(ctx cue.Context, prop apiextensions.JSONSchemaProps) *ast.Attribute { +func extensionAttribute(ctx *cue.Context, prop apiextensions.JSONSchemaProps) *ast.Attribute { a := attr{ name: "crd", fields: []keyval{}, diff --git a/encoding/crd/k8s/apiextensions/v1/types_jsonschema.go b/encoding/crd/k8s/apiextensions/v1/types_jsonschema.go index cff1d27924a..bab3a5712bd 100644 --- a/encoding/crd/k8s/apiextensions/v1/types_jsonschema.go +++ b/encoding/crd/k8s/apiextensions/v1/types_jsonschema.go @@ -16,6 +16,8 @@ limitations under the License. package v1 +import "encoding/json" + // FieldValueErrorReason is a machine-readable value providing more detail about why a field failed the validation. // +enum type FieldValueErrorReason string @@ -76,7 +78,7 @@ type JSONSchemaProps struct { // default is a default value for undefined object fields. // Defaulting is a beta feature under the CustomResourceDefaulting feature gate. // Defaulting requires spec.preserveUnknownFields to be false. - Default *JSON `json:"default,omitempty" protobuf:"bytes,8,opt,name=default"` + Default *json.RawMessage `json:"default,omitempty" protobuf:"bytes,8,opt,name=default"` Maximum *float64 `json:"maximum,omitempty" protobuf:"bytes,9,opt,name=maximum"` ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty" protobuf:"bytes,10,opt,name=exclusiveMaximum"` Minimum *float64 `json:"minimum,omitempty" protobuf:"bytes,11,opt,name=minimum"` @@ -88,7 +90,7 @@ type JSONSchemaProps struct { MinItems *int64 `json:"minItems,omitempty" protobuf:"bytes,17,opt,name=minItems"` UniqueItems bool `json:"uniqueItems,omitempty" protobuf:"bytes,18,opt,name=uniqueItems"` MultipleOf *float64 `json:"multipleOf,omitempty" protobuf:"bytes,19,opt,name=multipleOf"` - Enum []JSON `json:"enum,omitempty" protobuf:"bytes,20,rep,name=enum"` + Enum []json.RawMessage `json:"enum,omitempty" protobuf:"bytes,20,rep,name=enum"` MaxProperties *int64 `json:"maxProperties,omitempty" protobuf:"bytes,21,opt,name=maxProperties"` MinProperties *int64 `json:"minProperties,omitempty" protobuf:"bytes,22,opt,name=minProperties"` Required []string `json:"required,omitempty" protobuf:"bytes,23,rep,name=required"` @@ -104,7 +106,7 @@ type JSONSchemaProps struct { AdditionalItems *JSONSchemaPropsOrBool `json:"additionalItems,omitempty" protobuf:"bytes,33,opt,name=additionalItems"` Definitions JSONSchemaDefinitions `json:"definitions,omitempty" protobuf:"bytes,34,opt,name=definitions"` ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty" protobuf:"bytes,35,opt,name=externalDocs"` - Example *JSON `json:"example,omitempty" protobuf:"bytes,36,opt,name=example"` + Example *json.RawMessage `json:"example,omitempty" protobuf:"bytes,36,opt,name=example"` Nullable bool `json:"nullable,omitempty" protobuf:"bytes,37,opt,name=nullable"` // x-kubernetes-preserve-unknown-fields stops the API server @@ -312,8 +314,8 @@ type JSONSchemaURL string // JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps // or an array of JSONSchemaProps. Mainly here for serialization purposes. type JSONSchemaPropsOrArray struct { - Schema *JSONSchemaProps `json:",inline" protobuf:"bytes,1,opt,name=schema"` - JSONSchemas []JSONSchemaProps `json:",inline" protobuf:"bytes,2,rep,name=jSONSchemas"` + Schema *JSONSchemaProps `protobuf:"bytes,1,opt,name=schema"` + JSONSchemas []JSONSchemaProps `protobuf:"bytes,2,rep,name=jSONSchemas"` } // OpenAPISchemaType is used by the kube-openapi generator when constructing @@ -332,8 +334,8 @@ func (_ JSONSchemaPropsOrArray) OpenAPISchemaFormat() string { return "" } // JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value. // Defaults to true for the boolean property. type JSONSchemaPropsOrBool struct { - Allows bool `json:",inline" protobuf:"varint,1,opt,name=allows"` - Schema *JSONSchemaProps `json:",inline" protobuf:"bytes,2,opt,name=schema"` + Allows bool `protobuf:"varint,1,opt,name=allows"` + Schema *JSONSchemaProps `protobuf:"bytes,2,opt,name=schema"` } // OpenAPISchemaType is used by the kube-openapi generator when constructing @@ -354,8 +356,8 @@ type JSONSchemaDependencies map[string]JSONSchemaPropsOrStringArray // JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array. type JSONSchemaPropsOrStringArray struct { - Schema *JSONSchemaProps `json:",inline" protobuf:"bytes,1,opt,name=schema"` - Property []string `json:",inline" protobuf:"bytes,2,rep,name=property"` + Schema *JSONSchemaProps `protobuf:"bytes,1,opt,name=schema"` + Property []string `protobuf:"bytes,2,rep,name=property"` } // OpenAPISchemaType is used by the kube-openapi generator when constructing diff --git a/encoding/crd/k8s/metav1/time.go b/encoding/crd/k8s/metav1/time.go index d380462723c..4125bac4af7 100644 --- a/encoding/crd/k8s/metav1/time.go +++ b/encoding/crd/k8s/metav1/time.go @@ -29,7 +29,7 @@ import ( // +protobuf.as=Timestamp // +protobuf.options.(gogoproto.goproto_stringer)=false type Time struct { - time.Time `json:",inline" protobuf:"-"` + time.Time `protobuf:"-"` } // DeepCopyInto creates a deep-copy of the Time value. The underlying time.Time diff --git a/encoding/crd/parse.go b/encoding/crd/parse.go index 80592fbdd56..0fb29936b64 100644 --- a/encoding/crd/parse.go +++ b/encoding/crd/parse.go @@ -21,7 +21,7 @@ import ( "cuelang.org/go/cue" apiextensions "cuelang.org/go/encoding/crd/k8s/apiextensions/v1" "cuelang.org/go/encoding/yaml" - goyaml "gopkg.in/yaml.v3" + kyaml "sigs.k8s.io/yaml" ) // Splits a YAML file containing one or more YAML documents into its elements @@ -108,7 +108,7 @@ func parseCRD(val cue.Value) (*apiextensions.CustomResourceDefinition, error) { // Decode into a v1.CustomResourceDefinition obj := &apiextensions.CustomResourceDefinition{} - err = goyaml.Unmarshal(d, obj) + err = kyaml.Unmarshal(d, obj) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 4c139b3d436..0eaf3398cb2 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( golang.org/x/text v0.13.0 golang.org/x/tools v0.14.0 gopkg.in/yaml.v3 v3.0.1 + sigs.k8s.io/yaml v1.4.0 ) require ( diff --git a/go.sum b/go.sum index a6b45587772..4e53a88391c 100644 --- a/go.sum +++ b/go.sum @@ -64,3 +64,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From c8ed80c2dd72c403d0e300069191c9a63e0dd982 Mon Sep 17 00:00:00 2001 From: Justen Stall Date: Tue, 19 Dec 2023 00:06:37 -0500 Subject: [PATCH 20/20] move crd command to import Signed-off-by: Justen Stall --- cmd/cue/cmd/get.go | 2 +- cmd/cue/cmd/get_crd.go | 54 ++++++++++++++++++++---------------------- cmd/cue/cmd/import.go | 2 ++ 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/cmd/cue/cmd/get.go b/cmd/cue/cmd/get.go index b4bfef0c130..e562f78d024 100644 --- a/cmd/cue/cmd/get.go +++ b/cmd/cue/cmd/get.go @@ -47,6 +47,6 @@ per language and are documented in the respective subcommands. return nil }), } - cmd.AddCommand(newGoCmd(c), newCrdCmd(c)) + cmd.AddCommand(newGoCmd(c)) return cmd } diff --git a/cmd/cue/cmd/get_crd.go b/cmd/cue/cmd/get_crd.go index c72c78dcc73..4e5fa7d0a41 100644 --- a/cmd/cue/cmd/get_crd.go +++ b/cmd/cue/cmd/get_crd.go @@ -21,46 +21,44 @@ import ( "strings" "cuelang.org/go/encoding/crd" - "github.com/spf13/cobra" ) -func newCrdCmd(c *Command) *cobra.Command { - cmd := &cobra.Command{ - Use: "crd [files]", - Short: "add Kubernetes CustomResourceDefinition dependencies to the current module", - Long: `crd converts Kubernetes resources defined by a CustomResourceDefinition into CUE definitions +// func newCrdCmd(c *Command) *cobra.Command { +// cmd := &cobra.Command{ +// Use: "crd [files]", +// Short: "add Kubernetes CustomResourceDefinition dependencies to the current module", +// Long: `crd converts Kubernetes resources defined by a CustomResourceDefinition into CUE definitions -The command "cue get crd" converts the Kubernetes CustomResourceDefinition -to CUE. The retrieved definitions are put in the CUE module's pkg -directory at the API group name of the corresponding resource. The converted -definitions are available to any CUE file within the CUE module by using -this name. +// The command "cue get crd" converts the Kubernetes CustomResourceDefinition +// to CUE. The retrieved definitions are put in the CUE module's pkg +// directory at the API group name of the corresponding resource. The converted +// definitions are available to any CUE file within the CUE module by using +// this name. -The CustomResourceDefinition is converted to CUE based on how it would be -interpreted by the Kubernetes API server. Definitions for a CRD with group -name "myresource.example.com" and version "v1" will be written to a CUE -file named myresource.example.com/v1/types_crd_gen.cue. +// The CustomResourceDefinition is converted to CUE based on how it would be +// interpreted by the Kubernetes API server. Definitions for a CRD with group +// name "myresource.example.com" and version "v1" will be written to a CUE +// file named myresource.example.com/v1/types_crd_gen.cue. -It is safe for users to add additional files to the generated directories, -as long as their name does not end with _gen.*. +// It is safe for users to add additional files to the generated directories, +// as long as their name does not end with _gen.*. +// Rules of Converting CustomResourceDefinitions to CUE -Rules of Converting CustomResourceDefinitions to CUE +// CustomResourceDefinitions are converted to cue structs adhering to the following conventions: -CustomResourceDefinitions are converted to cue structs adhering to the following conventions: +// - OpenAPIv3 schema is imported the same as "cue import openapi". - - OpenAPIv3 schema is imported the same as "cue import openapi". +// - The @x-kubernetes-validation attribute is added if the field utilizes the "x-kubernetes-validation" extension. +// `, - - The @x-kubernetes-validation attribute is added if the field utilizes the "x-kubernetes-validation" extension. -`, +// RunE: mkRunE(c, runImportCRD), +// } - RunE: mkRunE(c, runGetCRD), - } - - return cmd -} +// return cmd +// } -func runGetCRD(cmd *Command, args []string) error { +func runImportCRD(cmd *Command, args []string) error { decoder := crd.NewExtractor(cmd.ctx, "// cue get crd "+strings.Join(args, " ")) data, err := os.ReadFile(args[0]) diff --git a/cmd/cue/cmd/import.go b/cmd/cue/cmd/import.go index 80a669af4be..4e49aa69e3a 100644 --- a/cmd/cue/cmd/import.go +++ b/cmd/cue/cmd/import.go @@ -290,6 +290,8 @@ func runImport(cmd *Command, args []string) (err error) { c.fileFilter = `\.(json|jsonl|ldjson)$` case "yaml": c.fileFilter = `\.(yaml|yml)$` + case "crd": + return runImportCRD(cmd, args) case "text": c.fileFilter = `\.txt$` case "binary":