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
2 changes: 1 addition & 1 deletion cmd/crossplane/composition/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type generateCmd struct {
Name string `help:"Name prefix for the composition." optional:""`
Plural string `help:"Custom plural for the referenced kind." optional:""`
Path string `help:"Output file." optional:""`
ProjectFile string `default:"crossplane-project.yaml" help:"Path to project definition file." short:"f"`
ProjectFile string `default:"${project_file}" help:"Path to project definition file." short:"f"`
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`

projFS afero.Fs
Expand Down
6 changes: 3 additions & 3 deletions cmd/crossplane/dependency/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ var addHelp string

// addCmd adds a dependency to the current project.
type addCmd struct {
Package string `arg:"" help:"Package to add (xpkg OCI reference, k8s:<version>, git repository URL, or HTTP(S) URL)."`
ProjectFile string `default:"crossplane-project.yaml" help:"Path to project definition file." short:"f"`
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`
Package string `arg:"" help:"Package to add (xpkg OCI reference, k8s:<version>, git repository URL, or HTTP(S) URL)."`
ProjectFile string `default:"${project_file}" help:"Path to project definition file." short:"f"`
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`

// Flags for specific dependency types.
APIOnly bool `help:"Mark an xpkg dependency as API-only (not a runtime dependency)." name:"api-only"`
Expand Down
10 changes: 5 additions & 5 deletions cmd/crossplane/dependency/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ var updateHelp string

// updateCacheCmd updates the dependency cache by regenerating all schemas.
type updateCacheCmd struct {
ProjectFile string `default:"crossplane-project.yaml" help:"Path to project definition file." short:"f"`
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`
GitToken string `env:"CROSSPLANE_GIT_TOKEN" help:"Token for git HTTPS authentication."`
GitUsername string `default:"x-access-token" env:"CROSSPLANE_GIT_USERNAME" help:"Username for git HTTPS authentication."`
ProjectFile string `default:"${project_file}" help:"Path to project definition file." short:"f"`
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`
GitToken string `env:"CROSSPLANE_GIT_TOKEN" help:"Token for git HTTPS authentication."`
GitUsername string `default:"x-access-token" env:"CROSSPLANE_GIT_USERNAME" help:"Username for git HTTPS authentication."`
}

func (c *updateCacheCmd) Help() string {
Expand Down Expand Up @@ -116,7 +116,7 @@ var cleanHelp string

// cleanCacheCmd removes all generated schemas.
type cleanCacheCmd struct {
ProjectFile string `default:"crossplane-project.yaml" help:"Path to project definition file." short:"f"`
ProjectFile string `default:"${project_file}" help:"Path to project definition file." short:"f"`
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`
KeepPackages bool `help:"Keep cached xpkg package contents; remove only generated schemas." name:"keep-packages"`
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/crossplane/function/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ var (
)

type generateCmd struct {
Name string `arg:"" help:"Name of the function to generate. Must be a valid DNS-1035 label."`
PipelinePath string `arg:"" help:"Path to a Composition YAML file to add a pipeline step to." optional:""`
Language string `default:"go-templating" enum:"go,go-templating,kcl,python" help:"Language to use for the function." short:"l"`
ProjectFile string `default:"crossplane-project.yaml" help:"Path to project definition file." short:"f"`
Name string `arg:"" help:"Name of the function to generate. Must be a valid DNS-1035 label."`
PipelinePath string `arg:"" help:"Path to a Composition YAML file to add a pipeline step to." optional:""`
Language string `default:"go-templating" enum:"go,go-templating,kcl,python" help:"Language to use for the function." short:"l"`
ProjectFile string `default:"${project_file}" help:"Path to project definition file." short:"f"`

projFS afero.Fs
functionsFS afero.Fs
Expand Down
10 changes: 9 additions & 1 deletion cmd/crossplane/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
runtimexpkg "github.com/crossplane/crossplane-runtime/v2/pkg/xpkg"

"github.com/crossplane/cli/v2/cmd/crossplane/cluster"
"github.com/crossplane/cli/v2/cmd/crossplane/completion"
Expand All @@ -48,14 +49,20 @@ import (
"github.com/crossplane/cli/v2/internal/config"
"github.com/crossplane/cli/v2/internal/maturity"
"github.com/crossplane/cli/v2/internal/terminal"
clixpkg "github.com/crossplane/cli/v2/internal/xpkg"

_ "embed"
)

//go:embed help.md
var helpDescription string

var _ = kong.Must(&cli{})
var kongVars = kong.Vars{ //nolint:gochecknoglobals // We treat these as constants.
"project_file": clixpkg.ProjectFile,
"package_metadata_file": runtimexpkg.MetaFile,
}

var _ = kong.Must(&cli{}, kongVars)

type (
verboseFlag bool
Expand Down Expand Up @@ -128,6 +135,7 @@ func main() {
kong.BindTo(configcmd.ConfigPath(cfgPath), (*configcmd.ConfigPath)(nil)),
// Bind the loaded config so commands can read feature flags at runtime.
kong.Bind(cfg),
kongVars,
kong.Help(helpPrinter),
kong.UsageOnError())

Expand Down
2 changes: 1 addition & 1 deletion cmd/crossplane/project/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var buildHelp string

// buildCmd builds a project into Crossplane packages.
type buildCmd struct {
ProjectFile string `default:"crossplane-project.yaml" help:"Path to project definition." short:"f"`
ProjectFile string `default:"${project_file}" help:"Path to project definition." short:"f"`
Repository string `help:"Override the repository in the project file." optional:""`
OutputDir string `default:"_output" help:"Output directory for packages." short:"o"`
MaxConcurrency uint `default:"8" help:"Max concurrent function builds."`
Expand Down
7 changes: 3 additions & 4 deletions cmd/crossplane/project/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,14 @@ import (
"github.com/crossplane/crossplane-runtime/v2/pkg/errors"

"github.com/crossplane/cli/v2/internal/terminal"
clixpkg "github.com/crossplane/cli/v2/internal/xpkg"

_ "embed"
)

//go:embed help/init.md
var initHelp string

const projectFileName = "crossplane-project.yaml"

// initCmd initializes a new project.
type initCmd struct {
Name string `arg:"" help:"The name of the new project."`
Expand Down Expand Up @@ -82,7 +81,7 @@ func (c *initCmd) Run(sp terminal.SpinnerPrinter) error {
}

// Write a minimal crossplane-project.yaml.
projFile := filepath.Join(c.Directory, projectFileName)
projFile := filepath.Join(c.Directory, clixpkg.ProjectFile)
content := fmt.Sprintf(`apiVersion: dev.crossplane.io/v1alpha1
kind: Project
metadata:
Expand All @@ -92,7 +91,7 @@ spec:
`, c.Name, r.String())

if err := os.WriteFile(projFile, []byte(content), 0o600); err != nil {
return errors.Wrapf(err, "failed to write %s", projectFileName)
return errors.Wrapf(err, "failed to write %s", clixpkg.ProjectFile)
}

// Create default subdirectories.
Expand Down
2 changes: 1 addition & 1 deletion cmd/crossplane/project/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ var pushHelp string

// pushCmd pushes a built project to an OCI registry.
type pushCmd struct {
ProjectFile string `default:"crossplane-project.yaml" help:"Path to project definition." short:"f"`
ProjectFile string `default:"${project_file}" help:"Path to project definition." short:"f"`
Repository string `help:"Override the repository in the project file." optional:""`
Tag string `default:"" help:"Tag for the pushed package. Defaults to a time-based semver-like tag." short:"t"`
PackageFile string `help:"Package file to push. Defaults to <output-dir>/<project-name>.xpkg." optional:""`
Expand Down
8 changes: 4 additions & 4 deletions cmd/crossplane/project/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ var runHelp string

// runCmd builds a project and runs it in a local dev control plane.
type runCmd struct {
ProjectFile string `default:"crossplane-project.yaml" help:"Path to project definition." short:"f"`
Repository string `help:"Override the repository." optional:""`
MaxConcurrency uint `default:"8" help:"Max concurrent builds."`
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`
ProjectFile string `default:"${project_file}" help:"Path to project definition." short:"f"`
Repository string `help:"Override the repository." optional:""`
MaxConcurrency uint `default:"8" help:"Max concurrent builds."`
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`

ControlPlaneName string `help:"Name of the dev control plane. Defaults to project name."`
CrossplaneVersion string `help:"Version of Crossplane to install."`
Expand Down
2 changes: 1 addition & 1 deletion cmd/crossplane/project/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var stopHelp string

// stopCmd tears down a local dev control plane.
type stopCmd struct {
ProjectFile string `default:"crossplane-project.yaml" help:"Path to project definition." short:"f"`
ProjectFile string `default:"${project_file}" help:"Path to project definition." short:"f"`
ControlPlaneName string `help:"Name of the dev control plane. Defaults to project name."`
RegistryDir string `help:"Directory for local registry images."`
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/crossplane/render/op/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ type Cmd struct {
RequiredSchemas string `help:"A directory of JSON files specifying OpenAPI schemas to pass to the function pipeline." placeholder:"DIR" predictor:"directory" type:"path"`
WatchedResource string `help:"A YAML file specifying the watched resource for WatchOperation rendering. The resource is also added to required resources." placeholder:"PATH" predictor:"yaml_file" short:"w" type:"existingfile"`

CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`
MaxConcurrency uint `default:"8" help:"Maximum concurrency for building embedded functions."`
ProjectFile string `default:"crossplane-project.yaml" help:"Path to the project file. Optional." optional:"" predictor:"yaml_file" short:"f" type:"path"`
Timeout time.Duration `default:"1m" help:"How long to run before timing out."`
CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`
MaxConcurrency uint `default:"8" help:"Maximum concurrency for building embedded functions."`
ProjectFile string `default:"${project_file}" help:"Path to the project file. Optional." optional:"" predictor:"yaml_file" short:"f" type:"path"`
Timeout time.Duration `default:"1m" help:"How long to run before timing out."`

fs afero.Fs

Expand Down
104 changes: 79 additions & 25 deletions cmd/crossplane/render/xr/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
"github.com/crossplane/crossplane-runtime/v2/pkg/resource/unstructured/composed"
"github.com/crossplane/crossplane-runtime/v2/pkg/xcrd"
runtimexpkg "github.com/crossplane/crossplane-runtime/v2/pkg/xpkg"

apiextensionsv1 "github.com/crossplane/crossplane/apis/v2/apiextensions/v1"
pkgv1 "github.com/crossplane/crossplane/apis/v2/pkg/v1"
Expand Down Expand Up @@ -88,9 +89,9 @@ type Cmd struct {

CacheDir string `env:"CROSSPLANE_XPKG_CACHE" help:"Directory for cached xpkg package contents." name:"cache-dir"`
MaxConcurrency uint `default:"8" help:"Maximum concurrency for building embedded functions."`
ProjectFile string `default:"crossplane-project.yaml" help:"Path to the project file. Optional." optional:"" predictor:"yaml_file" short:"f" type:"path"`
ProjectFile string `help:"Path to the project file or package metadata file (crossplane.yaml). Autodetects the file type." optional:"" predictor:"yaml_file" short:"f" type:"path"`
Timeout time.Duration `default:"1m" help:"How long to run before timing out."`
XRD string `help:"A YAML file specifying the CompositeResourceDefinition (XRD) that defines the XR's schema and properties." optional:"" placeholder:"PATH" type:"existingfile"`
XRD string `help:"A YAML file specifying the CompositeResourceDefinition (XRD) that defines the XR's schema and properties." optional:"" placeholder:"PATH" type:"existingfile"`

fs afero.Fs

Expand Down Expand Up @@ -398,48 +399,83 @@ func (c *Cmd) loadFunctions(ctx context.Context, log logging.Logger, sp terminal
return fns, nil
}

projFilePath, err := filepath.Abs(c.ProjectFile)
filePath, err := c.resolveProjectFile()
if err != nil {
return nil, errors.Wrap(err, "cannot determine project file path")
return nil, err
}
projDir := filepath.Dir(projFilePath)

if _, err := os.Stat(projFilePath); err != nil {
return nil, errors.New("functions argument is required when not in a project")
dir := filepath.Dir(filePath)
fs := afero.NewBasePathFs(afero.NewOsFs(), dir)
fileName := filepath.Base(filePath)

isProject, err := projectfile.IsProjectFile(fs, fileName)
if err != nil {
return nil, errors.Wrapf(err, "cannot detect file type of %q", filePath)
}

log.Debug("Loading functions from project", "project-file", projFilePath)
if isProject {
return c.loadFunctionsFromProject(ctx, log, sp, cfg, fs, filePath, fileName)
}

projFS := afero.NewBasePathFs(afero.NewOsFs(), projDir)
proj, err := projectfile.Parse(projFS, filepath.Base(projFilePath))
if err != nil {
return nil, errors.Wrapf(err, "cannot parse project file %q", projFilePath)
return c.loadFunctionsFromConfiguration(ctx, log, fs, fileName)
}

// resolveProjectFile returns the absolute path of the project or configuration
// file to use. When the user supplied an explicit --project-file, that path is
// used as-is. Otherwise it probes for crossplane-project.yaml and then
// crossplane.yaml in the working directory.
func (c *Cmd) resolveProjectFile() (string, error) {
if c.ProjectFile != "" {
return filepath.Abs(c.ProjectFile)
}

for _, name := range []string{clixpkg.ProjectFile, runtimexpkg.MetaFile} {
abs, err := filepath.Abs(name)
if err != nil {
return "", errors.Wrapf(err, "cannot determine path for %q", name)
}
if _, err := os.Stat(abs); err == nil {
return abs, nil
}
}

return "", errors.New("functions argument is required when not in a project or configuration")
}

func (c *Cmd) newClientAndResolver(extraOpts ...clixpkg.ClientOption) (runtimexpkg.Client, *clixpkg.Resolver, error) {
cacheDir := c.CacheDir
if cacheDir == "" {
cacheDir = dependency.DefaultCacheDir()
}

xpkgClient, err := clixpkg.NewClient(
clixpkg.NewRemoteFetcher(),
clixpkg.WithCacheDir(afero.NewOsFs(), cacheDir),
clixpkg.WithImageConfigs(proj.Spec.ImageConfigs),
)
opts := append([]clixpkg.ClientOption{clixpkg.WithCacheDir(afero.NewOsFs(), cacheDir)}, extraOpts...)
xpkgClient, err := clixpkg.NewClient(clixpkg.NewRemoteFetcher(), opts...)
if err != nil {
return nil, errors.Wrap(err, "cannot create xpkg client")
return nil, nil, errors.Wrap(err, "cannot create xpkg client")
}
return xpkgClient, clixpkg.NewResolver(xpkgClient), nil
}

func (c *Cmd) loadFunctionsFromProject(ctx context.Context, log logging.Logger, sp terminal.SpinnerPrinter, cfg *config.Config, projFS afero.Fs, projFilePath, projFileName string) ([]pkgv1.Function, error) {
log.Debug("Loading functions from project", "project-file", projFilePath)

proj, err := projectfile.Parse(projFS, projFileName)
if err != nil {
return nil, errors.Wrapf(err, "cannot parse project file %q", projFilePath)
}

xpkgClient, resolver, err := c.newClientAndResolver(clixpkg.WithImageConfigs(proj.Spec.ImageConfigs))
if err != nil {
return nil, err
}
resolver := clixpkg.NewResolver(xpkgClient)

// Built here rather than alongside the schema manager below so the
// dependency manager generates dependency schemas the same way.
generators := generator.AllLanguages(
generator.WithGoModelAccessors(cfg.Features.GenerateGoModelAccessors),
generator.WithGoRuntimeObjects(cfg.Features.GenerateGoRuntimeObjects),
)

depMgr := dependency.NewManager(proj, projFS,
dependency.WithProjectFile(filepath.Base(projFilePath)),
dependency.WithProjectFile(projFileName),
dependency.WithSchemaGenerators(generators),
dependency.WithXpkgClient(xpkgClient),
dependency.WithResolver(resolver),
Expand All @@ -459,9 +495,6 @@ func (c *Cmd) loadFunctions(ctx context.Context, log logging.Logger, sp terminal
schemaRunner := runner.NewRealSchemaRunner(runner.WithImageConfig(proj.Spec.ImageConfigs))
schemaMgr := manager.New(schemasFS, generators, schemaRunner)

// The builder may decompress function runtime tarballs into this
// directory; the built images read from it lazily, so we remove it only
// after they have been written to the daemon below.
tempDir, err := os.MkdirTemp("", "crossplane-build-")
if err != nil {
return errors.Wrap(err, "failed to create temporary build directory")
Expand Down Expand Up @@ -494,3 +527,24 @@ func (c *Cmd) loadFunctions(ctx context.Context, log logging.Logger, sp terminal

return fns, nil
}

func (c *Cmd) loadFunctionsFromConfiguration(ctx context.Context, log logging.Logger, cfgFS afero.Fs, cfgFileName string) ([]pkgv1.Function, error) {
log.Debug("Loading functions from configuration file", "configuration-file", cfgFileName)

cfgMeta, err := clixpkg.ParseConfiguration(cfgFS, cfgFileName)
if err != nil {
return nil, errors.Wrapf(err, "cannot parse configuration file %q", cfgFileName)
}

_, resolver, err := c.newClientAndResolver()
if err != nil {
return nil, err
}

fns, err := clixpkg.ResolveConfigurationFunctions(ctx, cfgMeta, resolver)
if err != nil {
return nil, errors.Wrap(err, "cannot resolve function dependencies from configuration file")
}

return fns, nil
}
Loading
Loading