Check / check (pull_request) Skipped
Old images are now removed by their upaas-<app>:<N> tag, without force, so Docker deletes an image only when no other tag (such as another app's build of the same source) and no container still uses it. Removing by ID with force could delete another app's rollback image. The step after a deploy (record the new image, keep the replaced one for rollback, remove the rest) is its own function, tested against a fake Docker API, so the test fails if the removal is dropped or runs before the images are updated. Model: opus-5-5
107 lines
2.9 KiB
Go
107 lines
2.9 KiB
Go
package deploy_test
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/fx/fxtest"
|
|
|
|
"sneak.berlin/go/upaas/internal/config"
|
|
"sneak.berlin/go/upaas/internal/database"
|
|
"sneak.berlin/go/upaas/internal/docker"
|
|
"sneak.berlin/go/upaas/internal/logger"
|
|
"sneak.berlin/go/upaas/internal/models"
|
|
"sneak.berlin/go/upaas/internal/service/deploy"
|
|
)
|
|
|
|
// TestRecordDeployedImageRemovesOldImages runs the step after a deploy
|
|
// against a fake Docker API. Image one is also tagged for another app,
|
|
// image two was the previous image, three the current one, four is new.
|
|
func TestRecordDeployedImageRemovesOldImages(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
mu sync.Mutex
|
|
removed []string
|
|
forced bool
|
|
)
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(
|
|
func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
switch {
|
|
case r.Method == http.MethodDelete:
|
|
_, name, _ := strings.Cut(r.URL.Path, "/images/")
|
|
|
|
mu.Lock()
|
|
|
|
removed = append(removed, name)
|
|
forced = forced || r.URL.Query().Get("force") != ""
|
|
mu.Unlock()
|
|
|
|
_, _ = w.Write([]byte(`[]`))
|
|
case strings.HasSuffix(r.URL.Path, "/images/json"):
|
|
_, _ = w.Write([]byte(`[
|
|
{"Id":"sha256:one","RepoTags":["upaas-myapp:1","upaas-otherapp:7"]},
|
|
{"Id":"sha256:two","RepoTags":["upaas-myapp:2"]},
|
|
{"Id":"sha256:three","RepoTags":["upaas-myapp:3"]},
|
|
{"Id":"sha256:four","RepoTags":["upaas-myapp:4"]}
|
|
]`))
|
|
default:
|
|
_, _ = w.Write([]byte(`{}`))
|
|
}
|
|
},
|
|
))
|
|
t.Cleanup(srv.Close)
|
|
|
|
log := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
|
lifecycle := fxtest.NewLifecycle(t)
|
|
|
|
dockerClient, err := docker.New(lifecycle, docker.Params{
|
|
Logger: logger.NewForTest(log),
|
|
Config: &config.Config{DockerHost: "tcp://" + srv.Listener.Addr().String()},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
lifecycle.RequireStart()
|
|
t.Cleanup(lifecycle.RequireStop)
|
|
|
|
db := database.NewTestDatabase(t)
|
|
ctx := context.Background()
|
|
|
|
app := models.NewApp(db)
|
|
app.ID = "myapp-id"
|
|
app.Name = "myapp"
|
|
app.ImageID = sql.NullString{String: "sha256:three", Valid: true}
|
|
app.PreviousImageID = sql.NullString{String: "sha256:two", Valid: true}
|
|
require.NoError(t, app.Save(ctx))
|
|
|
|
deployment := models.NewDeployment(db)
|
|
deployment.AppID = app.ID
|
|
require.NoError(t, deployment.Save(ctx))
|
|
|
|
svc := deploy.NewTestServiceWithConfig(log, &config.Config{}, dockerClient)
|
|
|
|
err = svc.RecordDeployedImage(ctx, app, deployment, "sha256:four")
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, "sha256:four", app.ImageID.String)
|
|
assert.Equal(t, "sha256:three", app.PreviousImageID.String)
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
assert.Equal(t, []string{"upaas-myapp:1", "upaas-myapp:2"}, removed)
|
|
assert.False(t, forced, "old image tags must be removed without force")
|
|
}
|