Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 16 additions & 37 deletions cmd/crossplane/xpkg/get_crds.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package xpkg

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
Expand All @@ -26,7 +25,6 @@ import (
"github.com/alecthomas/kong"
"github.com/spf13/afero"
extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
runtimeSchema "k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/yaml"

"github.com/crossplane/crossplane-runtime/v2/pkg/errors"
Expand Down Expand Up @@ -184,48 +182,29 @@ func (c *getCRDsCmd) outputPath(flatName, group, version, kind, ext string) stri
// shared schema mutations from internal/schemas/generator for YAML language
// server compatibility (additionalProperties: false on object types, etc.).
func (c *getCRDsCmd) writeJSONSchemas(k *kong.Context, crds []*extv1.CustomResourceDefinition) error {
count := 0

for _, crd := range crds {
group := crd.Spec.Group
kind := crd.Spec.Names.Kind

for _, ver := range crd.Spec.Versions {
if ver.Schema == nil || ver.Schema.OpenAPIV3Schema == nil {
continue
}

gvk := runtimeSchema.GroupVersionKind{Group: group, Version: ver.Name, Kind: kind}
schema, err := generator.ToJSONSchema(ver.Schema.OpenAPIV3Schema, gvk)
if err != nil {
return errors.Wrapf(err, "cannot convert schema for %s/%s %s", group, ver.Name, kind)
}

data, err := json.MarshalIndent(schema, "", " ")
if err != nil {
return errors.Wrapf(err, "cannot marshal JSON Schema for %s/%s %s", group, ver.Name, kind)
}

flatName := fmt.Sprintf("%s_%s_%s", group, ver.Name, strings.ToLower(kind))
outPath := c.outputPath(flatName, group, ver.Name, kind, ".json")
schemas, err := generator.CRDsToJSONSchemas(crds)
if err != nil {
return err
}

if err := c.fs.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
return errors.Wrapf(err, "cannot create directory for %q", outPath)
}
for _, s := range schemas {
flatName := fmt.Sprintf("%s_%s_%s", s.Group, s.Version, strings.ToLower(s.Kind))
outPath := c.outputPath(flatName, s.Group, s.Version, s.Kind, ".json")

if err := afero.WriteFile(c.fs, outPath, data, 0o644); err != nil {
return errors.Wrapf(err, "cannot write JSON Schema to %q", outPath)
}
if err := c.fs.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
return errors.Wrapf(err, "cannot create directory for %q", outPath)
}

if _, err := fmt.Fprintf(k.Stdout, "wrote %s\n", outPath); err != nil {
return errors.Wrap(err, errWriteOutput)
}
if err := afero.WriteFile(c.fs, outPath, s.Data, 0o644); err != nil {
return errors.Wrapf(err, "cannot write JSON Schema to %q", outPath)
}

count++
if _, err := fmt.Fprintf(k.Stdout, "wrote %s\n", outPath); err != nil {
return errors.Wrap(err, errWriteOutput)
}
}

if _, err := fmt.Fprintf(k.Stdout, "Total %d JSON Schemas written to %s\n", count, c.OutputDir); err != nil {
if _, err := fmt.Fprintf(k.Stdout, "Total %d JSON Schemas written to %s\n", len(schemas), c.OutputDir); err != nil {
return errors.Wrap(err, errWriteOutput)
}

Expand Down
115 changes: 74 additions & 41 deletions cmd/crossplane/xrd/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package xrd

import (
"bytes"
"fmt"
"path/filepath"
"slices"
"strings"

"github.com/alecthomas/kong"
"github.com/spf13/afero"
Expand All @@ -34,13 +36,19 @@ import (
apiextensionsv2 "github.com/crossplane/crossplane/apis/v2/apiextensions/v2"

commonIO "github.com/crossplane/cli/v2/cmd/crossplane/convert/io"
"github.com/crossplane/cli/v2/internal/schemas/generator"

_ "embed"
)

//go:embed help/convert.md
var convertHelp string

type convertOutput struct {
output string
data []byte
}

type convertCmd struct {
// Arguments.
InputFile string `arg:"" default:"-" help:"The XRD YAML file to convert, or '-' for stdin." optional:"" predictor:"file" type:"path"`
Expand All @@ -51,6 +59,9 @@ type convertCmd struct {
OutputFile string `help:"The file to write the generated CRD YAML to. Legacy XRDs produce a multi-doc YAML stream (XR CRD + Claim CRD)." placeholder:"PATH" predictor:"file" short:"o" type:"path" xor:"output"`
OutputDir string `help:"A directory to write the generated CRDs to. Each CRD gets a separate file named after the CRD." placeholder:"DIR" predictor:"directory" type:"path" xor:"output"`

// Format flags.
JSONSchema bool `help:"Write JSON Schema files instead of CRDs. Useful for YAML language server integration." name:"json-schema"`

fs afero.Fs
}

Expand Down Expand Up @@ -89,67 +100,89 @@ func (c *convertCmd) Run(k *kong.Context) error {
return errors.Wrapf(err, "cannot derive CRDs from XRD %q", xrd.GetName())
}

switch {
case c.OutputDir != "":
if err := c.fs.MkdirAll(c.OutputDir, 0o755); err != nil {
return errors.Wrapf(err, "cannot create output directory %q", c.OutputDir)
}

for _, crd := range crds {
path := filepath.Join(c.OutputDir, crd.GetName()+".yaml")
if err := c.writeFile(path, []*extv1.CustomResourceDefinition{crd}); err != nil {
return err
}
}

return nil
var outputs []convertOutput
if c.JSONSchema {
outputs, err = toJSONSchemaOutputs(crds)
} else {
outputs, err = toCRDOutputs(crds)
}
if err != nil {
return err
}

case c.OutputFile != "":
return c.writeFile(c.OutputFile, crds)
return c.writeOutputs(k, outputs)
}

default:
data, err := marshalCRDs(crds)
func toCRDOutputs(crds []*extv1.CustomResourceDefinition) ([]convertOutput, error) {
outputs := make([]convertOutput, 0, len(crds))
for _, crd := range crds {
b, err := yaml.Marshal(crd)
if err != nil {
return err
}

if _, err := k.Stdout.Write(data); err != nil {
return errors.Wrap(err, "cannot write output")
return nil, errors.Wrapf(err, "cannot marshal CRD %q", crd.GetName())
}

return nil
outputs = append(outputs, convertOutput{
output: crd.GetName() + ".yaml",
data: append([]byte("---\n"), b...),
})
}
return outputs, nil
}

// writeFile marshals the given CRDs to a multi-doc YAML stream and writes it to path.
func (c *convertCmd) writeFile(path string, crds []*extv1.CustomResourceDefinition) error {
data, err := marshalCRDs(crds)
func toJSONSchemaOutputs(crds []*extv1.CustomResourceDefinition) ([]convertOutput, error) {
schemas, err := generator.CRDsToJSONSchemas(crds)
if err != nil {
return err
return nil, err
}

if err := afero.WriteFile(c.fs, path, data, 0o644); err != nil {
return errors.Wrapf(err, "cannot write output file %q", path)
outputs := make([]convertOutput, 0, len(schemas))
for _, s := range schemas {
outputs = append(outputs, convertOutput{
output: fmt.Sprintf("%s_%s_%s.json", s.Group, s.Version, strings.ToLower(s.Kind)),
data: s.Data,
})
}

return nil
return outputs, nil
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// marshalCRDs returns the multi-doc YAML stream for the given CRDs.
func marshalCRDs(crds []*extv1.CustomResourceDefinition) ([]byte, error) {
func (c *convertCmd) writeOutputs(k *kong.Context, outputs []convertOutput) error {
if c.OutputDir != "" {
if err := c.fs.MkdirAll(c.OutputDir, 0o755); err != nil {
return errors.Wrapf(err, "cannot create output directory %q", c.OutputDir)
}

for _, o := range outputs {
path := filepath.Join(c.OutputDir, o.output)
if err := afero.WriteFile(c.fs, path, o.data, 0o644); err != nil {
return errors.Wrapf(err, "cannot write output file %q", path)
}
}

return nil
}

if c.JSONSchema && len(outputs) > 1 {
return errors.Errorf("cannot write %d JSON Schemas to a single output; use --output-dir to write one file per schema", len(outputs))
}

var buf bytes.Buffer
for _, o := range outputs {
buf.Write(o.data)
}

for _, crd := range crds {
b, err := yaml.Marshal(crd)
if err != nil {
return nil, errors.Wrapf(err, "cannot marshal CRD %q", crd.GetName())
if c.OutputFile != "" {
if err := afero.WriteFile(c.fs, c.OutputFile, buf.Bytes(), 0o644); err != nil {
return errors.Wrapf(err, "cannot write output file %q", c.OutputFile)
}

buf.WriteString("---\n")
buf.Write(b)
return nil
}

return buf.Bytes(), nil
if _, err := k.Stdout.Write(buf.Bytes()); err != nil {
return errors.Wrap(err, "cannot write output")
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return nil
}

// toCRDs converts a Crossplane XRD into the Kubernetes CRDs that describe
Expand Down
Loading