diff --git a/blueprint/pkg/blueprint/raw.go b/blueprint/pkg/blueprint/raw.go index 88cad73e..6e44e251 100644 --- a/blueprint/pkg/blueprint/raw.go +++ b/blueprint/pkg/blueprint/raw.go @@ -1,18 +1,26 @@ package blueprint -import "cuelang.org/go/cue" +import ( + "cuelang.org/go/cue" + "github.com/input-output-hk/catalyst-forge/blueprint/schema" +) // RawBlueprint represents a raw (undecoded) blueprint. type RawBlueprint struct { value cue.Value } -// Decode decodes the raw blueprint into the given value. -func (r RawBlueprint) Decode(x interface{}) error { - return r.value.Decode(x) +// Decode decodes the raw blueprint into a schema.Blueprint. +func (r *RawBlueprint) Decode() (schema.Blueprint, error) { + var cfg schema.Blueprint + if err := r.value.Decode(&cfg); err != nil { + return schema.Blueprint{}, err + } + + return cfg, nil } -// DecodePath decodes a value from the raw blueprint. +// DecodePath decodes a path from the raw blueprint to the given interface. func (r RawBlueprint) DecodePath(path string, x interface{}) error { v := r.Get(path) return v.Decode(x) diff --git a/blueprint/pkg/loader/loader.go b/blueprint/pkg/loader/loader.go index 3f5f4602..a75af2bc 100644 --- a/blueprint/pkg/loader/loader.go +++ b/blueprint/pkg/loader/loader.go @@ -25,20 +25,27 @@ var ( ErrVersionNotFound = errors.New("version not found") ) -type BlueprintLoader struct { - blueprint blueprint.RawBlueprint - injector injector.Injector - logger *slog.Logger - rootPath string - walker walker.ReverseWalker +// BlueprintLoader is an interface for loading blueprints. +type BlueprintLoader interface { + + // Load loads the blueprint. + Load() blueprint.RawBlueprint +} + +// DefaultBlueprintLoader is the default implementation of the BlueprintLoader +type DefaultBlueprintLoader struct { + injector injector.Injector + logger *slog.Logger + rootPath string + walker walker.ReverseWalker } -func (b *BlueprintLoader) Load() error { +func (b *DefaultBlueprintLoader) Load() (blueprint.RawBlueprint, error) { b.logger.Info("Searching for git root", "rootPath", b.rootPath) gitRoot, err := b.findGitRoot(b.rootPath) if err != nil && !errors.Is(err, ErrGitRootNotFound) { b.logger.Error("Failed to find git root", "error", err) - return fmt.Errorf("failed to find git root: %w", err) + return blueprint.RawBlueprint{}, fmt.Errorf("failed to find git root: %w", err) } var files map[string][]byte @@ -47,14 +54,14 @@ func (b *BlueprintLoader) Load() error { files, err = b.findBlueprints(b.rootPath, b.rootPath) if err != nil { b.logger.Error("Failed to find blueprint files", "error", err) - return fmt.Errorf("failed to find blueprint files: %w", err) + return blueprint.RawBlueprint{}, fmt.Errorf("failed to find blueprint files: %w", err) } } else { b.logger.Info("Git root found, searching for blueprint files up to git root", "gitRoot", gitRoot) files, err = b.findBlueprints(b.rootPath, gitRoot) if err != nil { b.logger.Error("Failed to find blueprint files", "error", err) - return fmt.Errorf("failed to find blueprint files: %w", err) + return blueprint.RawBlueprint{}, fmt.Errorf("failed to find blueprint files: %w", err) } } @@ -62,7 +69,7 @@ func (b *BlueprintLoader) Load() error { schema, err := schema.LoadSchema(ctx) if err != nil { b.logger.Error("Failed to load schema", "error", err) - return fmt.Errorf("failed to load schema: %w", err) + return blueprint.RawBlueprint{}, fmt.Errorf("failed to load schema: %w", err) } var finalBlueprint cue.Value @@ -74,7 +81,7 @@ func (b *BlueprintLoader) Load() error { bp, err := blueprint.NewBlueprintFile(ctx, path, data, b.injector) if err != nil { b.logger.Error("Failed to load blueprint file", "path", path, "error", err) - return fmt.Errorf("failed to load blueprint file: %w", err) + return blueprint.RawBlueprint{}, fmt.Errorf("failed to load blueprint file: %w", err) } bps = append(bps, bp) @@ -82,13 +89,13 @@ func (b *BlueprintLoader) Load() error { if err := bps.ValidateMajorVersions(); err != nil { b.logger.Error("Major version mismatch") - return err + return blueprint.RawBlueprint{}, err } userBlueprint, err := bps.Unify(ctx) if err != nil { b.logger.Error("Failed to unify blueprint files", "error", err) - return fmt.Errorf("failed to unify blueprint files: %w", err) + return blueprint.RawBlueprint{}, fmt.Errorf("failed to unify blueprint files: %w", err) } finalVersion = bps.Version() @@ -102,7 +109,7 @@ func (b *BlueprintLoader) Load() error { if err := cuetools.Validate(finalBlueprint, cue.Concrete(true)); err != nil { b.logger.Error("Failed to validate full blueprint", "error", err) - return err + return blueprint.RawBlueprint{}, err } if err := version.ValidateVersions(finalVersion, schema.Version); err != nil { @@ -110,32 +117,17 @@ func (b *BlueprintLoader) Load() error { b.logger.Warn("The minor version of the blueprint is greater than the supported version", "version", finalVersion) } else { b.logger.Error("The major version of the blueprint is greater than the supported version", "version", finalVersion) - return fmt.Errorf("the major version of the blueprint (%s) is different than the supported version: cannot continue", finalVersion.String()) + return blueprint.RawBlueprint{}, fmt.Errorf("the major version of the blueprint (%s) is different than the supported version: cannot continue", finalVersion.String()) } } - b.blueprint = blueprint.NewRawBlueprint(finalBlueprint) - return nil -} - -func (b *BlueprintLoader) Decode() (schema.Blueprint, error) { - var cfg schema.Blueprint - if err := b.blueprint.Decode(&cfg); err != nil { - return schema.Blueprint{}, err - } - - return cfg, nil -} - -// Raw returns the raw blueprint CUE value. -func (b *BlueprintLoader) Raw() blueprint.RawBlueprint { - return b.blueprint + return blueprint.NewRawBlueprint(finalBlueprint), nil } // findBlueprints searches for blueprint files starting from the startPath and // ending at the endPath. It returns a map of blueprint file paths to their // contents or an error if the search fails. -func (b *BlueprintLoader) findBlueprints(startPath, endPath string) (map[string][]byte, error) { +func (b *DefaultBlueprintLoader) findBlueprints(startPath, endPath string) (map[string][]byte, error) { bps := make(map[string][]byte) err := b.walker.Walk( @@ -174,7 +166,7 @@ func (b *BlueprintLoader) findBlueprints(startPath, endPath string) (map[string] // findGitRoot finds the root of a Git repository starting from the given // path. It returns the path to the root of the Git repository or an error if // the root is not found. -func (b *BlueprintLoader) findGitRoot(startPath string) (string, error) { +func (b *DefaultBlueprintLoader) findGitRoot(startPath string) (string, error) { var gitRoot string err := b.walker.Walk( startPath, @@ -202,34 +194,19 @@ func (b *BlueprintLoader) findGitRoot(startPath string) (string, error) { return gitRoot, nil } -// NewDefaultBlueprintLoader creates a new blueprint loader with default -// settings and an optional logger. +// NewDefaultBlueprintLoader creates a new DefaultBlueprintLoader. func NewDefaultBlueprintLoader(rootPath string, logger *slog.Logger, -) BlueprintLoader { +) DefaultBlueprintLoader { if logger == nil { logger = slog.New(slog.NewTextHandler(io.Discard, nil)) } walker := walker.NewDefaultFSReverseWalker(logger) - return BlueprintLoader{ + return DefaultBlueprintLoader{ injector: injector.NewDefaultInjector(logger), logger: logger, rootPath: rootPath, walker: &walker, } } - -// NewBlueprintLoader creates a new blueprint loader -func NewBlueprintLoader(rootPath string, - logger *slog.Logger, - walker walker.ReverseWalker, - injector injector.Injector, -) BlueprintLoader { - return BlueprintLoader{ - injector: injector, - logger: logger, - rootPath: rootPath, - walker: walker, - } -} diff --git a/blueprint/pkg/loader/loader_test.go b/blueprint/pkg/loader/loader_test.go index 2edb1f0e..dae37f09 100644 --- a/blueprint/pkg/loader/loader_test.go +++ b/blueprint/pkg/loader/loader_test.go @@ -9,7 +9,6 @@ import ( "strings" "testing" - "cuelang.org/go/cue" "github.com/input-output-hk/catalyst-forge/blueprint/pkg/injector" imocks "github.com/input-output-hk/catalyst-forge/blueprint/pkg/injector/mocks" "github.com/input-output-hk/catalyst-forge/tools/pkg/testutils" @@ -230,7 +229,7 @@ func TestBlueprintLoaderLoad(t *testing.T) { }, } - loader := BlueprintLoader{ + loader := DefaultBlueprintLoader{ injector: injector.NewInjector( slog.New(slog.NewTextHandler(io.Discard, nil)), &imocks.EnvGetterMock{ @@ -244,13 +243,13 @@ func TestBlueprintLoaderLoad(t *testing.T) { walker: walker, } - err := loader.Load() + bp, err := loader.Load() if testutils.AssertError(t, err, tt.expectErr, "") { return } for _, test := range tt.want { - value := loader.blueprint.Value().LookupPath(cue.ParsePath(test.fieldPath)) + value := bp.Get(test.fieldPath) assert.Nil(t, value.Err(), "failed to lookup field %s: %v", test.fieldPath, value.Err()) switch test.fieldType { @@ -330,7 +329,7 @@ func TestBlueprintLoader_findBlueprints(t *testing.T) { }, } - loader := BlueprintLoader{ + loader := DefaultBlueprintLoader{ walker: walker, } got, err := loader.findBlueprints("/tmp", "/tmp") @@ -400,7 +399,7 @@ func TestBlueprintLoader_findGitRoot(t *testing.T) { }, } - loader := BlueprintLoader{ + loader := DefaultBlueprintLoader{ walker: walker, } got, err := loader.findGitRoot(tt.start) diff --git a/forge/cli/cmd/cmds/blueprint_dump.go b/forge/cli/cmd/cmds/blueprint_dump.go index 42c74d2d..0aa6f1de 100644 --- a/forge/cli/cmd/cmds/blueprint_dump.go +++ b/forge/cli/cmd/cmds/blueprint_dump.go @@ -4,26 +4,24 @@ import ( "fmt" "log/slog" "os" - - "github.com/input-output-hk/catalyst-forge/blueprint/pkg/loader" ) type DumpCmd struct { - Config string `arg:"" help:"Path to the blueprint file."` - Pretty bool `help:"Pretty print JSON output."` + Blueprint string `arg:"" help:"Path to the blueprint file."` + Pretty bool `help:"Pretty print JSON output."` } func (c *DumpCmd) Run(logger *slog.Logger) error { - if _, err := os.Stat(c.Config); os.IsNotExist(err) { - return fmt.Errorf("blueprint file does not exist: %s", c.Config) + if _, err := os.Stat(c.Blueprint); os.IsNotExist(err) { + return fmt.Errorf("blueprint file does not exist: %s", c.Blueprint) } - loader := loader.NewDefaultBlueprintLoader(c.Config, logger) - if err := loader.Load(); err != nil { - return err + rbp, err := loadRawBlueprint(c.Blueprint, logger) + if err != nil { + return fmt.Errorf("could not load blueprint: %w", err) } - json, err := loader.Raw().MarshalJSON() + json, err := rbp.MarshalJSON() if err != nil { return err } diff --git a/forge/cli/cmd/cmds/secret.go b/forge/cli/cmd/cmds/secret.go index fe403f80..1961eec9 100644 --- a/forge/cli/cmd/cmds/secret.go +++ b/forge/cli/cmd/cmds/secret.go @@ -6,7 +6,6 @@ import ( "log/slog" "strings" - "github.com/input-output-hk/catalyst-forge/blueprint/pkg/loader" "github.com/input-output-hk/catalyst-forge/blueprint/schema" "github.com/input-output-hk/catalyst-forge/forge/cli/pkg/secrets" ) @@ -41,13 +40,11 @@ func (c *Get) Run(logger *slog.Logger) error { var maps map[string]string if c.Blueprint != "" { - loader := loader.NewDefaultBlueprintLoader(c.Blueprint, logger) - if err := loader.Load(); err != nil { + rbp, err := loadRawBlueprint(c.Blueprint, logger) + if err != nil { return fmt.Errorf("could not load blueprint: %w", err) } - rbp := loader.Raw() - var secret schema.Secret if err := rbp.DecodePath(c.Path, &secret); err != nil { return fmt.Errorf("could not decode secret: %w", err) @@ -132,13 +129,11 @@ func (c *Set) Run(logger *slog.Logger) error { var path, provider string if c.Blueprint != "" { - loader := loader.NewDefaultBlueprintLoader(c.Blueprint, logger) - if err := loader.Load(); err != nil { + rbp, err := loadRawBlueprint(c.Blueprint, logger) + if err != nil { return fmt.Errorf("could not load blueprint: %w", err) } - rbp := loader.Raw() - var secret schema.Secret if err := rbp.DecodePath(c.Path, &secret); err != nil { return fmt.Errorf("could not decode secret: %w", err) diff --git a/forge/cli/cmd/cmds/util.go b/forge/cli/cmd/cmds/util.go index e21d73dc..fa680cbd 100644 --- a/forge/cli/cmd/cmds/util.go +++ b/forge/cli/cmd/cmds/util.go @@ -5,7 +5,8 @@ import ( "fmt" "log/slog" - blueprint "github.com/input-output-hk/catalyst-forge/blueprint/pkg/loader" + "github.com/input-output-hk/catalyst-forge/blueprint/pkg/blueprint" + "github.com/input-output-hk/catalyst-forge/blueprint/pkg/loader" "github.com/input-output-hk/catalyst-forge/blueprint/schema" "github.com/input-output-hk/catalyst-forge/forge/cli/pkg/earthly" ) @@ -86,19 +87,23 @@ func generateOpts(target string, flags *RunCmd, config *schema.Blueprint) []eart // loadBlueprint loads the blueprint file from the given root path. func loadBlueprint(rootPath string, logger *slog.Logger) (schema.Blueprint, error) { - loader := blueprint.NewDefaultBlueprintLoader(rootPath, logger) - - err := loader.Load() + raw, err := loadRawBlueprint(rootPath, logger) if err != nil { return schema.Blueprint{}, fmt.Errorf("failed loading blueprint: %w", err) } - config, err := loader.Decode() + bp, err := raw.Decode() if err != nil { return schema.Blueprint{}, fmt.Errorf("failed decoding blueprint: %w", err) } - return config, nil + return bp, nil +} + +// loadRawBlueprint loads the raw blueprint file from the given root path. +func loadRawBlueprint(rootPath string, logger *slog.Logger) (blueprint.RawBlueprint, error) { + loader := loader.NewDefaultBlueprintLoader(rootPath, logger) + return loader.Load() } // printJson prints the given data as a JSON string.