diff --git a/features/check-recipes.feature b/features/check-recipes.feature index 5c872670..c60616ae 100644 --- a/features/check-recipes.feature +++ b/features/check-recipes.feature @@ -12,7 +12,49 @@ Feature: Check for new recipe versions When I change recipe "foo" to version "v0.0.2" And I push the recipe "foo" to the local OCI repository And I check new versions for recipe "foo" - Then CLI produced an output "New versions found" + Then CLI produced an output "new versions found: v0.0.2" + + Scenario: Find multiple newer version for a recipe + Given a project directory + And a recipes directory + And a recipe "foo" that generates file "README.md" + And a local OCI registry + When I execute recipe "foo" + And the source of the sauce with recipe "foo" is in the local OCI registry + Then execution of the recipe has succeeded + When I push the recipe "foo" to the local OCI repository + Then no errors were printed + When I change recipe "foo" to version "v0.0.2" + And I push the recipe "foo" to the local OCI repository + And I change recipe "foo" to version "v0.0.3" + And I push the recipe "foo" to the local OCI repository + Then I check new versions for recipe "foo" + Then CLI produced an output "new versions found: v0.0.2, v0.0.3" + + Scenario: Find newer version for multiple recipes + Given a project directory + And a recipes directory + And a recipe "foo" that generates file "foo.md" + And a recipe "bar" that generates file "bar.md" + And a local OCI registry + When I execute recipe "foo" + And the source of the sauce with recipe "foo" is in the local OCI registry + Then execution of the recipe has succeeded + When I execute recipe "bar" + And the source of the sauce with recipe "bar" is in the local OCI registry + Then execution of the recipe has succeeded + When I push the recipe "foo" to the local OCI repository + When I push the recipe "bar" to the local OCI repository + Then no errors were printed + When I change recipe "foo" to version "v0.0.2" + And I push the recipe "foo" to the local OCI repository + Then no errors were printed + When I change recipe "bar" to version "v0.0.2" + And I push the recipe "bar" to the local OCI repository + Then no errors were printed + Then I check new versions + Then CLI produced an output "foo: new versions found: v0.0.2" + And CLI produced an output "bar: new versions found: v0.0.2" Scenario: Unable to find newer recipe versions Given a project directory @@ -27,7 +69,29 @@ Feature: Check for new recipe versions And the source of the sauce with recipe "foo" is in the local OCI registry Then execution of the recipe has succeeded And I check new versions for recipe "foo" - Then CLI produced an output "No new versions found" + Then CLI produced an output "no new versions found" + + Scenario: Unable to find newer recipe versions for all recipes + Given a project directory + And a recipes directory + And a recipe "foo" that generates file "foo.md" + And a recipe "bar" that generates file "bar.md" + And a local OCI registry + When I execute recipe "foo" + And the source of the sauce with recipe "foo" is in the local OCI registry + Then execution of the recipe has succeeded + When I execute recipe "bar" + And the source of the sauce with recipe "bar" is in the local OCI registry + Then execution of the recipe has succeeded + When I push the recipe "foo" to the local OCI repository + When I push the recipe "bar" to the local OCI repository + Then no errors were printed + When I change recipe "foo" to version "v0.0.2" + And I push the recipe "foo" to the local OCI repository + Then no errors were printed + Then I check new versions + Then CLI produced an output "foo: new versions found: v0.0.2" + And CLI produced an output "bar: no new versions found" Scenario: Executing remote recipe automatically adds the repo as source for the sauce Given a project directory @@ -41,4 +105,4 @@ Feature: Check for new recipe versions Then execution of the recipe has succeeded # Note the lack of explicitly setting the source for the sauce And I check new versions for recipe "foo" - Then CLI produced an output "New versions found" \ No newline at end of file + Then CLI produced an output "new versions found: v0.0.2" \ No newline at end of file diff --git a/internal/cli/check.go b/internal/cli/check.go index d4d702cc..927ec384 100644 --- a/internal/cli/check.go +++ b/internal/cli/check.go @@ -3,12 +3,12 @@ package cli import ( "context" "fmt" + "strings" "github.com/futurice/jalapeno/internal/cli/option" - "github.com/futurice/jalapeno/pkg/oci" re "github.com/futurice/jalapeno/pkg/recipe" + "github.com/futurice/jalapeno/pkg/recipeutil" "github.com/spf13/cobra" - "golang.org/x/mod/semver" ) type checkOptions struct { @@ -19,7 +19,11 @@ type checkOptions struct { option.OCIRepository } -const DetailedExitCodeWhenUpdatesAvailable = 2 +const ( + ExitCodeOK = 0 + ExitCodeError = 0 + ExitCodeUpdatesAvailable = 2 +) func NewCheckCmd() *cobra.Command { var opts checkOptions @@ -45,7 +49,7 @@ jalapeno check --recipe my-recipe`, } cmd.Flags().StringVar(&opts.RecipeName, "recipe", "", "Name of the recipe to check for new versions") - cmd.Flags().BoolVar(&opts.UseDetailedExitCode, "detailed-exitcode", false, fmt.Sprintf("Returns a detailed exit code when the command exits. When provided, this argument changes the exit codes and their meanings to provide more granular information about what the resulting plan contains: 0 = Succeeded with no updates available, 1 = Error, %d = Succeeded with updates available", DetailedExitCodeWhenUpdatesAvailable)) + cmd.Flags().BoolVar(&opts.UseDetailedExitCode, "detailed-exitcode", false, fmt.Sprintf("Returns a detailed exit code when the command exits. When provided, this argument changes the exit codes and their meanings to provide more granular information about what the resulting plan contains: 0 = Succeeded with no updates available, 1 = Error, %d = Succeeded with updates available", ExitCodeUpdatesAvailable)) if err := option.ApplyFlags(&opts, cmd.Flags()); err != nil { return nil @@ -82,48 +86,34 @@ func runCheck(cmd *cobra.Command, opts checkOptions) error { cmd.Println("Checking for new versions...") - updates := make([][]string, len(sauces)) - for i, sauce := range sauces { - if sauce.CheckFrom == "" { - return fmt.Errorf("source of the sauce with ID '%s' is undefined, can not check for new versions", sauce.ID) - } - - repo, err := oci.NewRepository(opts.Repository(sauce.CheckFrom)) - + updatesAvailable, errorsFound := false, false + for _, sauce := range sauces { + versions, err := recipeutil.CheckForUpdates(sauce, opts.OCIRepository) if err != nil { - return err - } + errorsFound = true + cmd.Printf("%s: can not check for updates: %s\n", sauce.Recipe.Name, err) - ctx := context.Background() - err = repo.Tags(ctx, "", func(tags []string) error { - updates[i] = make([]string, 0, len(tags)) - for _, tag := range tags { - if semver.IsValid(tag) && semver.Compare(tag, sauce.Recipe.Version) > 0 { - updates[i] = append(updates[i], tag) - } - } - semver.Sort(updates[i]) - return nil - }) + } else if len(versions) > 0 { + updatesAvailable = true + cmd.Printf("%s: new versions found: %s\n", sauce.Recipe.Name, strings.Join(versions, ", ")) - if err != nil { - return err + } else { + cmd.Printf("%s: no new versions found\n", sauce.Recipe.Name) } } - for i, u := range updates { - if len(u) > 0 { - cmd.Printf("New versions found for recipe '%s': %s\n", sauces[i].Recipe.Name, u) - cmd.Println("Upgrade recipe with `jalapeno upgrade TODO`") - - if opts.UseDetailedExitCode { - ctx := context.WithValue(cmd.Context(), ExitCodeContextKey{}, DetailedExitCodeWhenUpdatesAvailable) - cmd.SetContext(ctx) - } - } else { - cmd.Printf("No new versions found for recipe '%s'\n", sauces[i].Recipe.Name) - } + var exitCode int + switch { + case errorsFound: + exitCode = ExitCodeError + case updatesAvailable && opts.UseDetailedExitCode: + exitCode = ExitCodeUpdatesAvailable + default: + exitCode = ExitCodeOK } + ctx := context.WithValue(cmd.Context(), ExitCodeContextKey{}, exitCode) + cmd.SetContext(ctx) + return nil } diff --git a/internal/cli/check_test.go b/internal/cli/check_test.go index 4bc15942..184e0424 100644 --- a/internal/cli/check_test.go +++ b/internal/cli/check_test.go @@ -7,16 +7,15 @@ import ( re "github.com/futurice/jalapeno/pkg/recipe" ) -func iRunCheck(ctx context.Context, recipe string) (context.Context, error) { +func iRunCheck(ctx context.Context) (context.Context, error) { projectDir := ctx.Value(projectDirectoryPathCtxKey{}).(string) ociRegistry := ctx.Value(ociRegistryCtxKey{}).(OCIRegistry) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) ctx, cmd := wrapCmdOutputs(ctx) args := []string{ "check", - fmt.Sprintf("--recipe=%s", recipe), fmt.Sprintf("--dir=%s", projectDir), } @@ -26,10 +25,8 @@ func iRunCheck(ctx context.Context, recipe string) (context.Context, error) { args = append(args, "--plain-http=true") } - if flagsAreSet && optionalFlags != nil { - for name, value := range optionalFlags { - args = append(args, fmt.Sprintf("--%s=%s", name, value)) - } + for name, value := range additionalFlags { + args = append(args, fmt.Sprintf("--%s=%s", name, value)) } cmd.SetArgs(args) @@ -37,12 +34,12 @@ func iRunCheck(ctx context.Context, recipe string) (context.Context, error) { return ctx, nil } -func newRecipeVersionsWereFound(ctx context.Context) (context.Context, error) { - return ctx, expectGivenOutput(ctx, "New versions found") -} +func iRunCheckForRecipe(ctx context.Context, recipe string) (context.Context, error) { + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) + additionalFlags["recipe"] = recipe -func noNewRecipeVersionsWereFound(ctx context.Context) (context.Context, error) { - return ctx, expectGivenOutput(ctx, "No new versions found") + ctx = context.WithValue(ctx, cmdAdditionalFlagsCtxKey{}, additionalFlags) + return iRunCheck(ctx) } func sourceOfTheSauceIsTheLocalOCIRegistry(ctx context.Context, recipeName string) (context.Context, error) { diff --git a/internal/cli/cmds_test.go b/internal/cli/cmds_test.go index 8da91f6c..c5557f6c 100644 --- a/internal/cli/cmds_test.go +++ b/internal/cli/cmds_test.go @@ -40,7 +40,7 @@ type ( ociRegistryCtxKey struct{} cmdStdOutCtxKey struct{} cmdStdErrCtxKey struct{} - cmdOptionalFlagsCtxKey struct{} + cmdAdditionalFlagsCtxKey struct{} dockerResourcesCtxKey struct{} ) @@ -80,9 +80,8 @@ func TestFeatures(t *testing.T) { s.Step(`^CLI produced an output "([^"]*)"$`, expectGivenOutput) s.Step(`^CLI produced an error "([^"]*)"$`, expectGivenError) s.Step(`^I change recipe "([^"]*)" to version "([^"]*)"$`, iChangeRecipeToVersion) - s.Step(`^I check new versions for recipe "([^"]*)"$`, iRunCheck) - s.Step(`^newer recipe versions were found`, newRecipeVersionsWereFound) - s.Step(`^no newer recipe versions were found`, noNewRecipeVersionsWereFound) + s.Step(`^I check new versions$`, iRunCheck) + s.Step(`^I check new versions for recipe "([^"]*)"$`, iRunCheckForRecipe) s.Step(`^I upgrade recipe "([^"]*)"$`, iRunUpgrade) s.Step(`^I upgrade recipe from the local OCI repository "([^"]*)"$`, iRunUpgradeFromRemoteRecipe) s.Step(`^I create a recipe with name "([^"]*)"$`, iRunCreate) @@ -105,6 +104,13 @@ func TestFeatures(t *testing.T) { s.Step(`^there should not be a sauce directory in the project directory$`, thereShouldNotBeASauceDirectoryInTheProjectDirectory) s.Step(`I check why the file "([^"]*)" is created$`, iRunWhy) s.Step(`I validate recipe "([^"]*)"$`, iRunValidate) + s.Before(func(ctx context.Context, sc *godog.Scenario) (context.Context, error) { + // Initialize additional flags to empty map before each step + return context.WithValue( + ctx, + cmdAdditionalFlagsCtxKey{}, + make(map[string]string)), nil + }) s.After(cleanDockerResources) s.After(cleanTempDirs) }, diff --git a/internal/cli/execute_test.go b/internal/cli/execute_test.go index b4c1c4fe..94260efb 100644 --- a/internal/cli/execute_test.go +++ b/internal/cli/execute_test.go @@ -10,7 +10,7 @@ import ( func iRunExecute(ctx context.Context, recipe string) (context.Context, error) { projectDir := ctx.Value(projectDirectoryPathCtxKey{}).(string) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) ctx, cmd := wrapCmdOutputs(ctx) @@ -28,10 +28,8 @@ func iRunExecute(ctx context.Context, recipe string) (context.Context, error) { fmt.Sprintf("--dir=%s", projectDir), } - if flagsAreSet && optionalFlags != nil { - for name, value := range optionalFlags { - args = append(args, fmt.Sprintf("--%s=%s", name, value)) - } + for name, value := range additionalFlags { + args = append(args, fmt.Sprintf("--%s=%s", name, value)) } cmd.SetArgs(args) @@ -42,13 +40,7 @@ func iRunExecute(ctx context.Context, recipe string) (context.Context, error) { func iExecuteRemoteRecipe(ctx context.Context, repository string) (context.Context, error) { ociRegistry := ctx.Value(ociRegistryCtxKey{}).(OCIRegistry) configDir, configFileExists := ctx.Value(dockerConfigDirectoryPathCtxKey{}).(string) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) - var flags map[string]string - if flagsAreSet { - flags = optionalFlags - } else { - flags = make(map[string]string) - } + flags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) url := fmt.Sprintf("oci://%s/%s", ociRegistry.Resource.GetHostPort("5000/tcp"), repository) @@ -68,7 +60,7 @@ func iExecuteRemoteRecipe(ctx context.Context, repository string) (context.Conte flags["registry-config"] = filepath.Join(configDir, DOCKER_CONFIG_FILENAME) } - ctx = context.WithValue(ctx, cmdOptionalFlagsCtxKey{}, flags) + ctx = context.WithValue(ctx, cmdAdditionalFlagsCtxKey{}, flags) return iRunExecute(ctx, url) } diff --git a/internal/cli/pull_test.go b/internal/cli/pull_test.go index 7b7221d8..bda4dbfe 100644 --- a/internal/cli/pull_test.go +++ b/internal/cli/pull_test.go @@ -10,7 +10,7 @@ func iPullRecipe(ctx context.Context, recipeName, repoName string) (context.Cont recipesDir := ctx.Value(recipesDirectoryPathCtxKey{}).(string) ociRegistry := ctx.Value(ociRegistryCtxKey{}).(OCIRegistry) configDir, configFileExists := ctx.Value(dockerConfigDirectoryPathCtxKey{}).(string) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) ctx, cmd := wrapCmdOutputs(ctx) @@ -39,10 +39,8 @@ func iPullRecipe(ctx context.Context, recipeName, repoName string) (context.Cont ) } - if flagsAreSet && optionalFlags != nil { - for name, value := range optionalFlags { - args = append(args, fmt.Sprintf("--%s=%s", name, value)) - } + for name, value := range additionalFlags { + args = append(args, fmt.Sprintf("--%s=%s", name, value)) } cmd.SetArgs(args) diff --git a/internal/cli/push_test.go b/internal/cli/push_test.go index afbcfacb..85736adc 100644 --- a/internal/cli/push_test.go +++ b/internal/cli/push_test.go @@ -10,7 +10,7 @@ func iRunPush(ctx context.Context, recipeName string) (context.Context, error) { recipesDir := ctx.Value(recipesDirectoryPathCtxKey{}).(string) ociRegistry := ctx.Value(ociRegistryCtxKey{}).(OCIRegistry) configDir, configFileExists := ctx.Value(dockerConfigDirectoryPathCtxKey{}).(string) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) ctx, cmd := wrapCmdOutputs(ctx) @@ -39,10 +39,8 @@ func iRunPush(ctx context.Context, recipeName string) (context.Context, error) { ) } - if flagsAreSet && optionalFlags != nil { - for name, value := range optionalFlags { - args = append(args, fmt.Sprintf("--%s=%s", name, value)) - } + for name, value := range additionalFlags { + args = append(args, fmt.Sprintf("--%s=%s", name, value)) } cmd.SetArgs(args) diff --git a/internal/cli/test_test.go b/internal/cli/test_test.go index e67adaf5..62a191ba 100644 --- a/internal/cli/test_test.go +++ b/internal/cli/test_test.go @@ -8,7 +8,7 @@ import ( func iRunTest(ctx context.Context, recipe string) (context.Context, error) { recipesDir := ctx.Value(recipesDirectoryPathCtxKey{}).(string) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) ctx, cmd := wrapCmdOutputs(ctx) @@ -17,10 +17,8 @@ func iRunTest(ctx context.Context, recipe string) (context.Context, error) { filepath.Join(recipesDir, recipe), } - if flagsAreSet && optionalFlags != nil { - for name, value := range optionalFlags { - args = append(args, fmt.Sprintf("--%s=%s", name, value)) - } + for name, value := range additionalFlags { + args = append(args, fmt.Sprintf("--%s=%s", name, value)) } cmd.SetArgs(args) @@ -31,7 +29,7 @@ func iRunTest(ctx context.Context, recipe string) (context.Context, error) { func iCreateRecipeTestUsingCLI(ctx context.Context, recipe string) (context.Context, error) { ctx = context.WithValue( ctx, - cmdOptionalFlagsCtxKey{}, + cmdAdditionalFlagsCtxKey{}, map[string]string{"create": "true"}, ) diff --git a/internal/cli/upgrade_test.go b/internal/cli/upgrade_test.go index e2950634..51a3af20 100644 --- a/internal/cli/upgrade_test.go +++ b/internal/cli/upgrade_test.go @@ -9,12 +9,12 @@ import ( "regexp" "strings" - re "github.com/futurice/jalapeno/pkg/recipe" + "github.com/futurice/jalapeno/pkg/recipe" ) func iRunUpgrade(ctx context.Context, recipe string) (context.Context, error) { projectDir := ctx.Value(projectDirectoryPathCtxKey{}).(string) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) ctx, cmd := wrapCmdOutputs(ctx) @@ -32,10 +32,8 @@ func iRunUpgrade(ctx context.Context, recipe string) (context.Context, error) { fmt.Sprintf("--dir=%s", projectDir), } - if flagsAreSet && optionalFlags != nil { - for name, value := range optionalFlags { - args = append(args, fmt.Sprintf("--%s=%s", name, value)) - } + for name, value := range additionalFlags { + args = append(args, fmt.Sprintf("--%s=%s", name, value)) } cmd.SetArgs(args) @@ -46,33 +44,27 @@ func iRunUpgrade(ctx context.Context, recipe string) (context.Context, error) { func iRunUpgradeFromRemoteRecipe(ctx context.Context, repository string) (context.Context, error) { registry := ctx.Value(ociRegistryCtxKey{}).(OCIRegistry) configDir, configFileExists := ctx.Value(dockerConfigDirectoryPathCtxKey{}).(string) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) - var flags map[string]string - if flagsAreSet { - flags = optionalFlags - } else { - flags = make(map[string]string) - } + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) url := fmt.Sprintf("oci://%s/%s", registry.Resource.GetHostPort("5000/tcp"), repository) if registry.TLSEnabled { // Allow self-signed certificates - flags["insecure"] = "true" + additionalFlags["insecure"] = "true" } else { - flags["plain-http"] = "true" + additionalFlags["plain-http"] = "true" } if registry.AuthEnabled { - flags["username"] = "foo" - flags["password"] = "bar" + additionalFlags["username"] = "foo" + additionalFlags["password"] = "bar" } if configFileExists && os.Getenv("DOCKER_CONFIG") == "" { - flags["registry-config"] = filepath.Join(configDir, DOCKER_CONFIG_FILENAME) + additionalFlags["registry-config"] = filepath.Join(configDir, DOCKER_CONFIG_FILENAME) } - ctx = context.WithValue(ctx, cmdOptionalFlagsCtxKey{}, flags) + ctx = context.WithValue(ctx, cmdAdditionalFlagsCtxKey{}, additionalFlags) return iRunUpgrade(ctx, url) } @@ -109,15 +101,15 @@ func iChangeRecipeTemplateToRender(ctx context.Context, recipeName, filename, co func iChangeRecipeToVersion(ctx context.Context, recipeName, version string) (context.Context, error) { recipesDir := ctx.Value(recipesDirectoryPathCtxKey{}).(string) - recipeFile := filepath.Join(recipesDir, recipeName, re.RecipeFileName+re.YAMLExtension) - recipeData, err := os.ReadFile(recipeFile) + + re, err := recipe.LoadRecipe(filepath.Join(recipesDir, recipeName)) if err != nil { return ctx, err } - newData := strings.Replace(string(recipeData), "v0.0.1", version, 1) + re.Version = version - if err := os.WriteFile(filepath.Join(recipesDir, recipeName, re.RecipeFileName+re.YAMLExtension), []byte(newData), 0644); err != nil { + if err = re.Save(recipesDir); err != nil { return ctx, err } diff --git a/internal/cli/validate_test.go b/internal/cli/validate_test.go index b5f17a3d..18fa08b1 100644 --- a/internal/cli/validate_test.go +++ b/internal/cli/validate_test.go @@ -7,7 +7,7 @@ import ( func iRunValidate(ctx context.Context, recipe string) (context.Context, error) { recipesDir := ctx.Value(recipesDirectoryPathCtxKey{}).(string) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) ctx, cmd := wrapCmdOutputs(ctx) @@ -17,10 +17,8 @@ func iRunValidate(ctx context.Context, recipe string) (context.Context, error) { fmt.Sprintf("--dir=%s", recipesDir), } - if flagsAreSet && optionalFlags != nil { - for name, value := range optionalFlags { - args = append(args, fmt.Sprintf("--%s=%s", name, value)) - } + for name, value := range additionalFlags { + args = append(args, fmt.Sprintf("--%s=%s", name, value)) } cmd.SetArgs(args) diff --git a/internal/cli/why_test.go b/internal/cli/why_test.go index dd38ec71..9193dbe0 100644 --- a/internal/cli/why_test.go +++ b/internal/cli/why_test.go @@ -7,7 +7,7 @@ import ( func iRunWhy(ctx context.Context, file string) (context.Context, error) { projectDir := ctx.Value(projectDirectoryPathCtxKey{}).(string) - optionalFlags, flagsAreSet := ctx.Value(cmdOptionalFlagsCtxKey{}).(map[string]string) + additionalFlags := ctx.Value(cmdAdditionalFlagsCtxKey{}).(map[string]string) ctx, cmd := wrapCmdOutputs(ctx) @@ -17,10 +17,8 @@ func iRunWhy(ctx context.Context, file string) (context.Context, error) { fmt.Sprintf("--dir=%s", projectDir), } - if flagsAreSet && optionalFlags != nil { - for name, value := range optionalFlags { - args = append(args, fmt.Sprintf("--%s=%s", name, value)) - } + for name, value := range additionalFlags { + args = append(args, fmt.Sprintf("--%s=%s", name, value)) } cmd.SetArgs(args) diff --git a/pkg/recipe/saver.go b/pkg/recipe/saver.go index dde51960..5d58e666 100644 --- a/pkg/recipe/saver.go +++ b/pkg/recipe/saver.go @@ -17,14 +17,16 @@ const ( // Save saves recipe to given destination func (re *Recipe) Save(dest string) error { // TODO: Make sure recipe name is path friendly - recipeDir := filepath.Join(dest, re.Name) + if filepath.Base(dest) != re.Name { + dest = filepath.Join(dest, re.Name) + } - err := os.MkdirAll(recipeDir, defaultFileMode) + err := os.MkdirAll(dest, defaultFileMode) if err != nil { - return fmt.Errorf("can not create directory %s: %v", recipeDir, err) + return fmt.Errorf("can not create directory %s: %v", dest, err) } - recipeFilepath := filepath.Join(recipeDir, RecipeFileName+YAMLExtension) + recipeFilepath := filepath.Join(dest, RecipeFileName+YAMLExtension) file, err := os.Create(recipeFilepath) if err != nil { return fmt.Errorf("failed to create recipe file: %w", err) @@ -43,12 +45,12 @@ func (re *Recipe) Save(dest string) error { return fmt.Errorf("failed to close recipe file: %w", err) } - err = re.saveTemplates(recipeDir) + err = re.saveTemplates(dest) if err != nil { return fmt.Errorf("can not save recipe templates: %w", err) } - err = re.saveTests(recipeDir) + err = re.saveTests(dest) if err != nil { return fmt.Errorf("can not save recipe tests: %w", err) } diff --git a/pkg/recipeutil/check.go b/pkg/recipeutil/check.go new file mode 100644 index 00000000..cc615fed --- /dev/null +++ b/pkg/recipeutil/check.go @@ -0,0 +1,41 @@ +package recipeutil + +import ( + "context" + "fmt" + + "github.com/futurice/jalapeno/internal/cli/option" + "github.com/futurice/jalapeno/pkg/oci" + re "github.com/futurice/jalapeno/pkg/recipe" + "golang.org/x/mod/semver" +) + +func CheckForUpdates(sauce *re.Sauce, opts option.OCIRepository) ([]string, error) { + if sauce.CheckFrom == "" { + return nil, fmt.Errorf("source of the sauce with ID '%s' is undefined, can not check for new versions", sauce.ID) + } + + repo, err := oci.NewRepository(opts.Repository(sauce.CheckFrom)) + if err != nil { + return nil, err + } + + var versions []string + ctx := context.Background() + err = repo.Tags(ctx, "", func(tags []string) error { + versions = make([]string, 0, len(tags)) + for _, tag := range tags { + if semver.IsValid(tag) && semver.Compare(tag, sauce.Recipe.Version) > 0 { + versions = append(versions, tag) + } + } + semver.Sort(versions) + return nil + }) + + if err != nil { + return nil, err + } + + return versions, nil +}