This commit is contained in:
2026-10-09 21:05:56 -04:00
parent 9e2b36eb27
commit ce8fc1ca89
4 changed files with 2 additions and 265 deletions
-1
View File
@@ -7,4 +7,3 @@
/bin
dist/
tests/integration/.bats
.profile/
+2 -2
View File
@@ -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"
-260
View File
@@ -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)
}
})
}
}
-2
View File
@@ -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)