diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a31d4b2..525ae90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,5 +13,7 @@ jobs: - uses: actions/checkout@v3 - name: build run: docker compose --env-file=test/compose/.env.test -f test/compose/docker-compose.yml run composectl make + - name: prepare test env + run: docker compose --env-file=test/compose/.env.test -f test/compose/docker-compose.yml run composectl test/fixtures/images/build-images.sh - name: test run: docker compose --env-file=test/compose/.env.test -f test/compose/docker-compose.yml run composectl go test -v ./... diff --git a/test/fixtures/images/build-images.sh b/test/fixtures/images/build-images.sh new file mode 100755 index 0000000..10feeca --- /dev/null +++ b/test/fixtures/images/build-images.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +DIR="$(dirname "$(realpath "$0")")" +REGISTRY_URL="registry:5000" +IMAGE_NAME="factory/runner-image" +IMAGE_TAG="v0.1" +IMAGE_URI="${REGISTRY_URL}/${IMAGE_NAME}:${IMAGE_TAG}" + +# Function to check if the image exists in the registry +check_image() { + local response=$(curl -s -o /dev/null -w "%{http_code}" \ + "https://${REGISTRY_URL}/v2/${IMAGE_NAME}/manifests/${IMAGE_TAG}") + + if [[ "$response" == "200" || "$response" == "302" ]]; then + return 0 + else + return 1 + fi +} + +# Using the function in an if statement with negation +if ! check_image; then + docker buildx build -t "${IMAGE_URI}" --push $DIR/runner + # Remove image from a local dockerd store, the tests need it to be in the registry + docker image rm ${IMAGE_URI} +else + echo "Image ${IMAGE_URI} exists in the registry." +fi diff --git a/test/fixtures/images/runner/Dockerfile b/test/fixtures/images/runner/Dockerfile new file mode 100644 index 0000000..794cf74 --- /dev/null +++ b/test/fixtures/images/runner/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:alpine AS builder +WORKDIR /app +COPY forever.go . +RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o forever forever.go + +FROM scratch +COPY --from=builder /app/forever /forever +CMD ["/forever"] diff --git a/test/fixtures/images/runner/forever.go b/test/fixtures/images/runner/forever.go new file mode 100644 index 0000000..0d974f2 --- /dev/null +++ b/test/fixtures/images/runner/forever.go @@ -0,0 +1,11 @@ +package main + +import ( + "time" +) + +func main() { + for { + time.Sleep(1 * time.Minute) // Sleep for 1 hour in each iteration + } +} diff --git a/test/integration/edge_cases_test.go b/test/integration/edge_cases_test.go new file mode 100644 index 0000000..4e7a390 --- /dev/null +++ b/test/integration/edge_cases_test.go @@ -0,0 +1,106 @@ +package main + +import ( + "encoding/json" + "fmt" + composectl "github.com/foundriesio/composeapp/cmd/composectl/cmd" + "os" + "os/exec" + "path" + "testing" +) + +func TestAppImageMultiUse(t *testing.T) { + appName := "app1" + appBaseUri := "registry:5000/factory/" + appName + appComposeDef := ` +services: + srvs-01: + image: registry:5000/factory/runner-image:v0.1 + ports: + - 8080:80 + srvs-02: + image: registry:5000/factory/runner-image:v0.1 +` + appDir := path.Join(t.TempDir(), appName) + digestFile := path.Join(t.TempDir(), "app.sha256") + err := os.MkdirAll(appDir, 0o755) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile(path.Join(appDir, "docker-compose.yml"), []byte(appComposeDef), 0o640) + if err != nil { + t.Fatal(err) + } + + runCmd := func(t *testing.T, args ...string) []byte { + c := exec.Command(composeExec, args...) + c.Dir = appDir + output, err := c.CombinedOutput() + if err != nil { + t.Errorf("failed to run `%s` command: %s\n", args[0], output) + } + return output + } + + var appUri string + + removeApp := func(t *testing.T) { + t.Run("remove app", func(t *testing.T) { + runCmd(t, "rm", appUri) + }) + } + uninstallApp := func(t *testing.T) { + t.Run("uninstall app", func(t *testing.T) { + runCmd(t, "uninstall", appName) + }) + } + stopApp := func(t *testing.T) { + t.Run("stop app", func(t *testing.T) { + runCmd(t, "stop", appName) + }) + } + + t.Run("publish app", func(t *testing.T) { + runCmd(t, "publish", "-d", digestFile, appBaseUri+":asdsa", "amd64") + if b, err := os.ReadFile(digestFile); err == nil { + appUri = appBaseUri + "@" + string(b) + } else { + t.Errorf("failed to read the published app digest: %s\n", err) + } + fmt.Printf("published app uri: %s\n", appUri) + }) + + t.Run("pull app", func(t *testing.T) { + runCmd(t, "pull", appUri, "-u", "90") + }) + defer removeApp(t) + + t.Run("install app", func(t *testing.T) { + runCmd(t, "install", appUri) + }) + defer uninstallApp(t) + + t.Run("run app", func(t *testing.T) { + runCmd(t, "run", appName) + }) + defer stopApp(t) + + t.Run("check if running", func(t *testing.T) { + output := runCmd(t, "ps", appUri, "--format", "json") + var psOutput map[string]composectl.App + if err := json.Unmarshal(output, &psOutput); err != nil { + t.Errorf("failed to unmarshal app ps output: %s\n", err) + } + if len(psOutput) != 1 { + t.Errorf("expected one element in ps output, got: %d\n", len(psOutput)) + } + appStatus, ok := psOutput[appUri] + if !ok { + t.Errorf("no app URI in the ps output: %+v\n", psOutput) + } + if appStatus.State != "running" { + t.Errorf("app is not running, its state: %+s\n", appStatus.State) + } + }) +}