From 8d95f08c5ece8e6efaf235a2d1f56e340644b664 Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Thu, 23 Mar 2023 16:42:43 -0400 Subject: [PATCH] cli: allow setting empty name in options (to infer) The `ProjectOptions` includes config to pass to the loader, but most of the fields, including `Name`, are optional. If unset/empty, the loader will determine a project name (if possible). Allow (re-)setting it to empty here. This is mostly for convenience based on the way these helpers are used in practice, so that it's possible to pass a value from e.g. a CLI flag in, which will be empty if unset. Also added a bunch of documentation to the type. Signed-off-by: Milas Bowman --- cli/options.go | 60 ++++++++++++++++++++++++++++++++++++++------- cli/options_test.go | 27 +++++++++++++++++--- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/cli/options.go b/cli/options.go index ea22b007..d9b5bce0 100644 --- a/cli/options.go +++ b/cli/options.go @@ -23,23 +23,59 @@ import ( "path/filepath" "strings" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/compose-spec/compose-go/consts" "github.com/compose-spec/compose-go/dotenv" "github.com/compose-spec/compose-go/errdefs" "github.com/compose-spec/compose-go/loader" "github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/utils" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) -// ProjectOptions groups the command line options recommended for a Compose implementation +// ProjectOptions provides common configuration for loading a project. type ProjectOptions struct { - Name string - WorkingDir string + // Name is a valid Compose project name to be used or empty. + // + // If empty, the project loader will automatically infer a reasonable + // project name if possible. + Name string + + // WorkingDir is a file path to use as the project directory or empty. + // + // If empty, the project loader will automatically infer a reasonable + // working directory if possible. + WorkingDir string + + // ConfigPaths are file paths to one or more Compose files. + // + // These are applied in order by the loader following the merge logic + // as described in the spec. + // + // The first entry is required and is the primary Compose file. + // For convenience, WithConfigFileEnv and WithDefaultConfigPath + // are provided to populate this in a predictable manner. ConfigPaths []string + + // Environment are additional environment variables to make available + // for interpolation. + // + // NOTE: For security, the loader does not automatically expose any + // process environment variables. For convenience, WithOsEnv can be + // used if appropriate. Environment map[string]string - EnvFiles []string + + // EnvFiles are file paths to ".env" files with additional environment + // variable data. + // + // These are loaded in-order, so it is possible to override variables or + // in subsequent files. + // + // This field is optional, but any file paths that are included here must + // exist or an error will be returned during load. + EnvFiles []string + loadOptions []func(*loader.Options) } @@ -63,9 +99,15 @@ func NewProjectOptions(configs []string, opts ...ProjectOptionsFn) (*ProjectOpti // WithName defines ProjectOptions' name func WithName(name string) ProjectOptionsFn { return func(o *ProjectOptions) error { - normalized := loader.NormalizeProjectName(name) - if err := loader.CheckOriginalProjectNameIsNormalized(name, normalized); err != nil { - return err + // a project (once loaded) cannot have an empty name + // however, on the options object, the name is optional: if unset, + // a name will be inferred by the loader, so it's legal to set the + // name to an empty string here + if name != "" { + normalized := loader.NormalizeProjectName(name) + if err := loader.CheckOriginalProjectNameIsNormalized(name, normalized); err != nil { + return err + } } o.Name = name return nil diff --git a/cli/options_test.go b/cli/options_test.go index 7260f653..a5945818 100644 --- a/cli/options_test.go +++ b/cli/options_test.go @@ -22,9 +22,10 @@ import ( "path/filepath" "testing" + "gotest.tools/v3/assert" + "github.com/compose-spec/compose-go/consts" "github.com/compose-spec/compose-go/utils" - "gotest.tools/v3/assert" ) func TestProjectName(t *testing.T) { @@ -52,9 +53,27 @@ func TestProjectName(t *testing.T) { assert.Equal(t, p.Name, "42my_project_env") }) - t.Run("by name must not be empty", func(t *testing.T) { - _, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithName("")) - assert.ErrorContains(t, err, `project name must not be empty`) + t.Run("by name empty", func(t *testing.T) { + opts, err := NewProjectOptions( + []string{"testdata/simple/compose.yaml"}, + WithName(""), + ) + assert.NilError(t, err) + p, err := ProjectFromOptions(opts) + assert.NilError(t, err) + assert.Equal(t, p.Name, "simple") + }) + + t.Run("by name empty working dir", func(t *testing.T) { + opts, err := NewProjectOptions( + []string{"testdata/simple/compose.yaml"}, + WithName(""), + WithWorkingDirectory("/path/to/proj"), + ) + assert.NilError(t, err) + p, err := ProjectFromOptions(opts) + assert.NilError(t, err) + assert.Equal(t, p.Name, "proj") }) t.Run("by name must not come from root directory", func(t *testing.T) {