diff --git a/.gitignore b/.gitignore index 6a78b513..5efc96b2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ /bin dist/ tests/integration/.bats -.profile/ diff --git a/Makefile b/Makefile index d3839533..9a1a424c 100644 --- a/Makefile +++ b/Makefile @@ -40,13 +40,13 @@ check: (echo "gofmt: formatting issue - run 'make format' to resolve" && exit 1) test: - @go test ./... -short -failfast -cover -v -p 1 + @go test ./... -short -cover -v -p 1 integration-test: @go test ./... -short=false -failfast -cover -v -p 1 -count=1 find-tests: - @find . -iname "*_test.go" + @find . -name "*_test.go" find-integration-test: @find . -iname "*_e2e_test.go" diff --git a/cli/recipe/upgrade_e2e_test.go b/cli/recipe/upgrade_e2e_test.go deleted file mode 100644 index ccf244dc..00000000 --- a/cli/recipe/upgrade_e2e_test.go +++ /dev/null @@ -1,260 +0,0 @@ -package recipe - -import ( - "bytes" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - "testing" - - "coopcloud.tech/abra/cli/internal" - "coopcloud.tech/abra/pkg/client" - "coopcloud.tech/abra/pkg/config" - gitPkg "coopcloud.tech/abra/pkg/git" - "coopcloud.tech/abra/pkg/log" - recipePkg "coopcloud.tech/abra/pkg/recipe" - "coopcloud.tech/tagcmp" - "github.com/distribution/reference" -) - -func TestUpgradePinning(t *testing.T) { - if testing.Short() { - t.Skip("skipping e2e test in short mode") - } - - setupTestRecipeCatalogue(t) - // Test recipe is at nginx:1.21.6 and unpinned - // https://git.coopcloud.tech/toolshed/abra-test-recipe/commit/77d752b3c61a71bfb2c45f40ec07c9d626a0e580 - setupTestRecipe(t, "77d752b3c61a71bfb2c45f40ec07c9d626a0e580") - - // We should find nginx >1.21.6 (minor version bump), upgrade to - // it and pin to the digest. Test works by reading TUI output. - out := runUpgrade(t, "abra-test-recipe", false, true, true, true) - match := regexp.MustCompile(`tag upgraded and pinned from 1\.21\.6 to 1\.(\d+)\.\d+@sha256:[a-f0-9]{64}`).FindStringSubmatch(out) - if match == nil { - t.Fatalf("expected a pinned minor upgrade, got:\n%s", out) - } - if minor, _ := strconv.Atoi(match[1]); minor <= 21 { - t.Fatalf("expected a minor version above 1.21, got:\n%s", out) - } - image := getActualRecipeImage(t, "abra-test-recipe", "app") - if !regexp.MustCompile(`^nginx:1\.` + match[1] + `\.\d+@sha256:[a-f0-9]{64}$`).MatchString(image) { - t.Fatalf("got image %s, want nginx:1.%s.x@sha256:<64 hex>", image, match[1]) - } -} - -// Ensure that --pindigests should pin a digest even if the recipe is at the latest version already. -func TestPinningNoUpgrade(t *testing.T) { - if testing.Short() { - t.Skip("skipping e2e test in short mode") - } - - setupTestRecipeCatalogue(t) - // Test recipe is at nginx:1.21.6 and unpinned - // https://git.coopcloud.tech/toolshed/abra-test-recipe/commit/77d752b3c61a71bfb2c45f40ec07c9d626a0e580 - setupTestRecipe(t, "77d752b3c61a71bfb2c45f40ec07c9d626a0e580") - - // Upgrade with --pindigests should pin the current image, even if there's no new upgrade available. - out := runUpgrade(t, "abra-test-recipe", true, false, true, true) - if !strings.Contains(out, "tag upgraded and pinned from 1.21.6 to 1.21.6@sha256:") { - t.Fatalf("expected current image to be pinned, got:\n%s", out) - } - image := getActualRecipeImage(t, "abra-test-recipe", "app") - if !regexp.MustCompile(`^nginx:1\.21\.6@sha256:[a-f0-9]{64}$`).MatchString(image) { - t.Fatalf("got image %s, want nginx:1.21.6@sha256:<64 hex>", image) - } -} - -// Ensure that --pindigests works on recipes that have already pinned their digest -func TestOverwriteExistingPin(t *testing.T) { - if testing.Short() { - t.Skip("skipping e2e test in short mode") - } - - setupTestRecipeCatalogue(t) - // Test recipe is at nginx:1.21.5 and pinned - // https://git.coopcloud.tech/toolshed/abra-test-recipe/commit/4f0642719d2e2a568f4350cf7eea182438d58be3 - setupTestRecipe(t, "4f0642719d2e2a568f4350cf7eea182438d58be3") - - out := runUpgrade(t, "abra-test-recipe", true, false, false, true) - if !strings.Contains(out, "tag upgraded and pinned from 1.21.5 to 1.21.6@sha256:") { - t.Fatalf("should ignore --pindigests if recipe already pins, got:\n%s", out) - } - image := getActualRecipeImage(t, "abra-test-recipe", "app") - if !regexp.MustCompile(`^nginx:1\.21\.6@sha256:[a-f0-9]{64}$`).MatchString(image) { - t.Fatalf("got image %s, want 1.21.6@sha256...", image) - } - if strings.Contains(image, "0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31") { - t.Fatalf("updated image tag but kept digest: %s", image) - } -} - -func TestUpgradeAll(t *testing.T) { - t.Skip() // Massive integration test, all recipes. 15+ minutes to run. - if testing.Short() { - t.Skip("skipping e2e test in short mode") - } - - setupTestRecipeCatalogue(t) - - catalogue, err := recipePkg.ReadRecipeCatalogue(false) - if err != nil { - t.Fatal(err) - } - if len(catalogue) == 0 { - t.Fatal("recipe catalogue is empty") - } - - for _, rm := range catalogue { - t.Run(rm.Name, func(t *testing.T) { - recipe := recipePkg.Get(rm.Name) - if err := recipe.EnsureExists(); err != nil { - t.Fatal(err) - } - - runUpgrade(t, rm.Name, false, false, true, false) - }) - } -} - -func getActualRecipeImage(t *testing.T, recipe, service string) string { - t.Helper() - r := recipePkg.Get(recipe) - cfg, err := r.GetComposeConfig(nil) - if err != nil { - t.Fatal(err) - } - for _, s := range cfg.Services { - if s.Name == service { - return s.Image - } - } - t.Fatalf("service %s not found", service) - return "" -} - -// Use a temporary ABRA_DIR for the whole run -func TestMain(m *testing.M) { - dir, err := os.MkdirTemp("", "abra-test-") - if err != nil { - panic(err) - } - os.Setenv("ABRA_DIR", dir) - config.ABRA_DIR = dir - config.CATALOGUE_DIR = filepath.Join(dir, "catalogue") - config.RECIPES_JSON = filepath.Join(config.CATALOGUE_DIR, "recipes.json") - - code := m.Run() - os.RemoveAll(dir) - os.Exit(code) -} - -// Give each test its own empty recipes directory. -func setupTestRecipeCatalogue(t *testing.T) { - t.Helper() - config.RECIPES_DIR = t.TempDir() -} - -// Use a test recipe at the given commit. -func setupTestRecipe(t *testing.T, commit string) { - t.Helper() - trd := filepath.Join(config.RECIPES_DIR, "abra-test-recipe") - if err := gitPkg.Clone(trd, "https://git.coopcloud.tech/toolshed/abra-test-recipe"); err != nil { - t.Fatal(err) - } - - r := recipePkg.Get("abra-test-recipe") - if _, err := r.EnsureVersion(commit); err != nil { - t.Fatal(err) - } -} - -func runUpgrade(t *testing.T, recipe string, patch, minor, pinDigests, chaos bool) string { - t.Helper() - - internal.NoInput = true - internal.Offline = true - internal.Chaos = chaos - internal.Patch = patch - internal.Minor = minor - internal.PinDigests = pinDigests - - var out bytes.Buffer - log.Logger.SetOutput(&out) - defer log.Logger.SetOutput(os.Stdout) - oldFatal := log.Fatal - log.Fatal = func(message any, keyvals ...any) { - t.Fatalf("recipe upgrade %s: %v\n%s", recipe, message, out.String()) - } - defer func() { log.Fatal = oldFatal }() - - RecipeUpgradeCommand.Run(RecipeUpgradeCommand, []string{recipe}) - return out.String() -} - -func TestPinningWithNoRegistryUpgrades(t *testing.T) { - if testing.Short() { - t.Skip("skipping e2e test in short mode") - } - img, err := reference.ParseNormalizedNamed("nginx") - if err != nil { - t.Fatal(err) - } - tags, err := client.GetRegistryTags(img) - if err != nil { - t.Fatal(err) - } - var latest tagcmp.Tag - found := false - for _, value := range tags { - candidate, err := tagcmp.Parse(value) - if err != nil || candidate.Suffix != "" { - continue - } - if !found || latest.IsLessThan(candidate) { - latest = candidate - found = true - } - } - current := "nginx:" + latest.String() - for _, tc := range []struct { - name string - patch, pin, existing bool - }{ - {"unpinned skip", true, false, false}, - {"pin current", true, true, false}, - {"keep existing pin", true, false, true}, - {"pin without bump flag", false, true, false}, - } { - t.Run(tc.name, func(t *testing.T) { - setupTestRecipeCatalogue(t) - // https://git.coopcloud.tech/toolshed/abra-test-recipe/commit/77d752b3c61a71bfb2c45f40ec07c9d626a0e580 - setupTestRecipe(t, "77d752b3c61a71bfb2c45f40ec07c9d626a0e580") - r := recipePkg.Get("abra-test-recipe") - digest := "" - if tc.existing { - digest, err = client.GetImageMultiplatformDigest(img, latest.String()) - if err != nil { - t.Fatal(err) - } - } - if _, err := r.UpdateTag("nginx", latest.String(), digest); err != nil { - t.Fatal(err) - } - out := runUpgrade(t, "abra-test-recipe", tc.patch, false, tc.pin, true) - got := getActualRecipeImage(t, "abra-test-recipe", "app") - if tc.pin || tc.existing { - if !strings.HasPrefix(got, current+"@sha256:") { - t.Fatalf("expected current tag pinned, got %s", got) - } - } else if got != current { - t.Fatalf("got %s, want %s", got, current) - } - if strings.Contains(out, "can upgrade service:") { - t.Fatalf("reported nonexistent upgrades: %s", out) - } - }) - } -} diff --git a/pkg/client/registry.go b/pkg/client/registry.go index 0b9713c3..b142b610 100644 --- a/pkg/client/registry.go +++ b/pkg/client/registry.go @@ -46,8 +46,6 @@ func GetImageMultiplatformDigest(img reference.Named, tag string) (string, error return "", errors.New(i18n.G("failed to parse image %s:%s, saw: %s", img.Name(), tag, err.Error())) } - // Use the credentials of the docker cli, which enables using a private - // registry by running docker login on the machine that abra is executed. rc := regclient.New(regclient.WithDockerCreds()) m, err := rc.ManifestHead(context.Background(), r)